From 20f442c776ff20c81180b78b86faa3347fe30fd2 Mon Sep 17 00:00:00 2001 From: topiam Date: Mon, 12 Dec 2022 00:41:27 +0800 Subject: [PATCH] =?UTF-8?q?:tada:=20=E9=87=8D=E7=A3=85=E5=8F=91=E5=B8=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 34 + .mvn/jvm.config | 1 + .mvn/maven.config | 1 + .mvn/wrapper/maven-wrapper.jar | Bin 0 -> 59925 bytes .mvn/wrapper/maven-wrapper.properties | 2 + CHANGELOG.md | 0 LICENSE | 661 + README.md | 136 + eiam-alert/pom.xml | 34 + eiam-application/eiam-application-all/pom.xml | 67 + eiam-application/eiam-application-cas/pom.xml | 41 + .../cas/AbstractCasApplicationService.java | 46 + .../CasStandardApplicationServiceImpl.java | 155 + .../application/cas/package-info.java | 18 + .../employee/application/package-info.java | 18 + .../eiam-application-core/pom.xml | 34 + .../AbstractApplicationService.java | 136 + .../application/ApplicationService.java | 118 + .../application/ApplicationServiceLoader.java | 104 + .../application/Saml2ApplicationService.java | 46 + .../application/SamlRamRoleNameValueType.java | 44 + .../context/ApplicationContext.java | 71 + .../context/ApplicationContextHolder.java | 62 + .../application/context/package-info.java | 18 + .../exception/AppCertNotExistException.java | 32 + .../exception/AppConfigNotExistException.java | 32 + .../exception/AppNotEnableException.java | 32 + .../exception/AppNotExistException.java | 32 + .../AppTemplateNotExistException.java | 34 + .../ParseSaml2MetadataException.java | 31 + .../employee/application/package-info.java | 18 + .../eiam-application-form/pom.xml | 41 + .../form/AbstractFormApplicationService.java | 46 + .../FormStandardApplicationServiceImpl.java | 156 + .../form/model/AppFormConfigGetResult.java | 56 + .../application/form/model/package-info.java | 18 + .../application/form/package-info.java | 18 + eiam-application/eiam-application-jwt/pom.xml | 41 + .../jwt/AbstractJwtApplicationService.java | 46 + .../JwtStandardApplicationServiceImpl.java | 155 + .../jwt/model/AppJwtGetResult.java | 56 + .../application/jwt/model/package-info.java | 18 + .../application/jwt/package-info.java | 18 + .../employee/application/package-info.java | 18 + .../eiam-application-oidc/pom.xml | 41 + .../oidc/AbstractOidcApplicationService.java | 70 + .../OidcStandardApplicationServiceImpl.java | 291 + .../AppOidcStandardConfigConverter.java | 130 + .../model/AppOidcStandardConfigGetResult.java | 202 + .../model/AppOidcStandardSaveConfigParam.java | 151 + .../application/oidc/model/package-info.java | 18 + .../application/oidc/package-info.java | 18 + .../eiam-application-saml2/pom.xml | 41 + .../employee/application/package-info.java | 18 + .../saml2/AbstractSamlAppService.java | 348 + .../Saml2StandardApplicationServiceImpl.java | 256 + .../AppSaml2StandardConfigConverter.java | 117 + .../AppSaml2StandardConfigGetResult.java | 170 + .../AppSaml2StandardSaveConfigParam.java | 175 + .../saml2/model/Saml2ConverterUtils.java | 54 + .../saml2/model/Saml2ProtocolEndpoint.java | 64 + .../application/saml2/model/package-info.java | 18 + .../application/saml2/package-info.java | 18 + eiam-application/pom.xml | 66 + eiam-audit/pom.xml | 42 + .../employee/audit/annotation/Audit.java | 49 + .../audit/annotation/AuditAspect.java | 177 + .../annotation/AuditExpressionOperations.java | 45 + .../audit/annotation/AuditExpressionRoot.java | 57 + .../audit/annotation/package-info.java | 18 + .../ElasticsearchConfiguration.java | 217 + .../employee/audit/context/AuditContext.java | 186 + .../audit/controller/AuditController.java | 91 + .../controller/pojo/AuditDictResult.java | 63 + .../audit/controller/pojo/AuditListQuery.java | 80 + .../controller/pojo/AuditListResult.java | 105 + .../topiam/employee/audit/entity/Actor.java | 57 + .../entity/AuditElasticSearchEntity.java | 98 + .../employee/audit/entity/AuditEntity.java | 139 + .../topiam/employee/audit/entity/Event.java | 88 + .../employee/audit/entity/GeoLocation.java | 112 + .../topiam/employee/audit/entity/Target.java | 58 + .../employee/audit/entity/UserAgent.java | 54 + .../employee/audit/enums/EventStatus.java | 68 + .../employee/audit/enums/EventType.java | 551 + .../employee/audit/enums/TargetType.java | 145 + .../enums/converter/AuditTypeConverter.java | 67 + .../enums/converter/EventStatusConverter.java | 67 + .../enums/converter/TargetTypeConverter.java | 67 + .../employee/audit/event/AuditEvent.java | 59 + .../audit/event/AuditEventListener.java | 125 + .../audit/event/AuditEventPublish.java | 327 + .../audit/event/type/AccountEventType.java | 213 + .../audit/event/type/AppEventType.java | 165 + .../event/type/AuthenticationEventType.java | 82 + .../audit/event/type/OtherEventType.java | 46 + .../employee/audit/event/type/Resource.java | 75 + .../audit/event/type/SettingEventType.java | 314 + .../employee/audit/event/type/Type.java | 58 + .../topiam/employee/audit/package-info.java | 24 + .../audit/repository/AuditRepository.java | 52 + .../audit/repository/package-info.java | 18 + .../employee/audit/service/AuditService.java | 52 + .../service/converter/AuditDataConverter.java | 204 + .../audit/service/impl/AuditServiceImpl.java | 127 + .../eiam-authentication-all/pom.xml | 79 + .../eiam-authentication-core/pom.xml | 35 + .../AbstractIdentityProviderService.java | 31 + .../common/IdentityProviderService.java | 111 + .../common/IdentityProviderServiceLoader.java | 106 + .../common/config/IdentityProviderConfig.java | 40 + ...tityProviderTemplateNotExistException.java | 34 + ...ractIdpAuthenticationProcessingFilter.java | 210 + .../authentication/common/modal/IdpUser.java | 95 + .../authentication/common/package-info.java | 18 + .../common/service/UserIdpService.java | 74 + .../eiam-authentication-dingtalk/pom.xml | 43 + .../dingtalk/DingTalkIdpOauthConfig.java | 52 + .../dingtalk/DingTalkIdpScanCodeConfig.java | 52 + ...ingtalkOAuth2AuthenticationConfigurer.java | 92 + ...gtalkScanCodeAuthenticationConfigurer.java | 91 + .../DingTalkAuthenticationConstants.java | 34 + ...th2AuthorizationRequestRedirectFilter.java | 144 + .../DingtalkOauthAuthenticationFilter.java | 210 + .../DingtalkScanCodeAuthenticationFilter.java | 245 + ...ScanCodeAuthorizationRequestGetFilter.java | 172 + .../authentication/dingtalk/package-info.java | 18 + .../eiam-authentication-feishu/pom.xml | 42 + .../feishu/configurer/package-info.java | 18 + .../filter/FeiShuAuthenticationFilter.java | 70 + .../authentication/feishu/package-info.java | 18 + .../eiam-authentication-mfa/pom.xml | 42 + .../authentication/mfa/package-info.java | 18 + .../eiam-authentication-qq/pom.xml | 42 + .../authentication/qq/QqIdpOauthConfig.java | 52 + .../QqOauthAuthenticationConfigurer.java | 93 + .../constant/QqAuthenticationConstants.java | 44 + ...th2AuthorizationRequestRedirectFilter.java | 139 + .../QqOAuth2LoginAuthenticationFilter.java | 170 + .../authentication/qq/package-info.java | 18 + .../eiam-authentication-sms/pom.xml | 42 + .../SmsAuthenticationConfigurer.java | 78 + .../sms/configurer/package-info.java | 18 + .../sms/filter/SmsAuthenticationFilter.java | 148 + .../sms/filter/package-info.java | 18 + .../authentication/sms/package-info.java | 18 + .../eiam-authentication-wechat/pom.xml | 42 + .../wechat/WeChatIdpScanCodeConfig.java | 51 + ...eChatScanCodeAuthenticationConfigurer.java | 93 + .../WeChatAuthenticationConstants.java | 37 + ...odeAuthorizationRequestRedirectFilter.java | 175 + ...ChatScanCodeLoginAuthenticationFilter.java | 168 + .../authentication/wechat/package-info.java | 18 + .../eiam-authentication-wechatwork/pom.xml | 42 + .../WeChatWorkIdpScanCodeConfig.java | 58 + ...tWorkScanCodeAuthenticationConfigurer.java | 94 + .../WeChatWorkAuthenticationConstants.java | 37 + ...odeAuthorizationRequestRedirectFilter.java | 164 + ...WorkScanCodeLoginAuthenticationFilter.java | 197 + .../wechatwork/package-info.java | 18 + eiam-authentication/pom.xml | 54 + eiam-common/pom.xml | 62 + .../common/constants/AccountConstants.java | 79 + .../common/constants/AnalysisConstants.java | 39 + .../common/constants/AppConstants.java | 50 + .../common/constants/AuditConstants.java | 50 + .../constants/AuthenticationConstants.java | 36 + .../common/constants/AuthorizeConstants.java | 80 + .../common/constants/CaptchaConstants.java | 28 + .../common/constants/CommonConstants.java | 45 + .../constants/ConfigBeanNameConstants.java | 61 + .../common/constants/ProtocolConstants.java | 147 + .../common/constants/SessionConstants.java | 41 + .../common/constants/SettingConstants.java | 51 + .../common/constants/StorageConstants.java | 43 + .../common/entity/MailSendRecordEntity.java | 90 + .../common/entity/SmsSendRecordEntity.java | 96 + .../entity/account/OrganizationEntity.java | 143 + .../account/OrganizationMemberEntity.java | 90 + .../entity/account/UserDetailEntity.java | 101 + .../common/entity/account/UserEntity.java | 214 + .../entity/account/UserGroupEntity.java | 62 + .../entity/account/UserGroupMemberEntity.java | 54 + .../account/UserHistoryPasswordEntity.java | 72 + .../entity/account/UserIdpBindEntity.java | 86 + .../entity/account/po/UserIdpBindPo.java | 44 + .../common/entity/account/po/UserPO.java | 44 + .../query/UserGroupMemberListQuery.java | 55 + .../query/UserListNotInGroupQuery.java | 55 + .../entity/account/query/UserListQuery.java | 93 + .../common/entity/analysis/package-info.java | 18 + .../entity/app/AppAccessPolicyEntity.java | 62 + .../common/entity/app/AppAccountEntity.java | 61 + .../common/entity/app/AppCertEntity.java | 123 + .../employee/common/entity/app/AppEntity.java | 119 + .../entity/app/AppOidcConfigEntity.java | 143 + .../entity/app/AppPermissionActionEntity.java | 72 + .../entity/app/AppPermissionPolicyEntity.java | 79 + .../app/AppPermissionResourceEntity.java | 89 + .../entity/app/AppPermissionRoleEntity.java | 75 + .../entity/app/AppSaml2ConfigEntity.java | 248 + .../entity/app/po/AppAccessPolicyPO.java | 60 + .../common/entity/app/po/AppAccountPO.java | 61 + .../common/entity/app/po/AppOidcConfigPO.java | 75 + .../entity/app/po/AppPermissionPolicyPO.java | 93 + .../entity/app/po/AppSaml2ConfigPO.java | 81 + .../app/query/AppAccessPolicyQuery.java | 69 + .../entity/app/query/AppAccountQuery.java | 64 + .../entity/app/query/AppPolicyQuery.java | 83 + .../entity/app/standard/package-info.java | 18 + .../IdentityProviderEntity.java | 96 + .../identitysource/IdentitySourceEntity.java | 108 + .../IdentitySourceEventRecordEntity.java | 99 + .../IdentitySourceSyncHistoryEntity.java | 117 + .../IdentitySourceSyncRecordEntity.java | 91 + .../identitysource/config/JobConfig.java | 249 + .../identitysource/config/StrategyConfig.java | 87 + .../employee/common/entity/package-info.java | 18 + .../entity/setting/AdministratorEntity.java | 118 + .../entity/setting/MailTemplateEntity.java | 78 + .../common/entity/setting/SettingEntity.java | 63 + .../entity/setting/config/SmsConfig.java | 81 + .../common/enums/AuthenticationType.java | 86 + .../employee/common/enums/BaseEnum.java | 40 + .../common/enums/CaptchaProviderType.java | 87 + .../common/enums/CheckValidityType.java | 47 + .../employee/common/enums/DataOrigin.java | 106 + .../common/enums/EnumDeserializer.java | 72 + .../enums/IdentityProviderCategory.java | 93 + .../common/enums/IdentityProviderType.java | 133 + .../employee/common/enums/Language.java | 90 + .../common/enums/ListEnumDeserializer.java | 91 + .../employee/common/enums/MailType.java | 197 + .../common/enums/MessageCategory.java | 85 + .../common/enums/MessageNoticeChannel.java | 87 + .../employee/common/enums/MfaFactor.java | 89 + .../topiam/employee/common/enums/MfaMode.java | 88 + .../common/enums/OrganizationType.java | 92 + .../common/enums/PasswordStrength.java | 39 + .../common/enums/PermissionActionType.java | 82 + .../employee/common/enums/PolicyEffect.java | 79 + .../common/enums/PolicyObjectType.java | 83 + .../common/enums/PolicySubjectType.java | 92 + .../employee/common/enums/SecretType.java | 84 + .../topiam/employee/common/enums/SmsType.java | 147 + .../employee/common/enums/SyncStatus.java | 85 + .../employee/common/enums/TriggerType.java | 73 + .../employee/common/enums/UserGender.java | 77 + .../employee/common/enums/UserIdType.java | 91 + .../employee/common/enums/UserStatus.java | 92 + .../employee/common/enums/UserType.java | 90 + .../common/enums/app/AppCertUsingType.java | 76 + .../common/enums/app/AppProtocol.java | 88 + .../employee/common/enums/app/AppType.java | 101 + .../common/enums/app/AttributeNameFormat.java | 72 + .../enums/app/AuthnContextClassRefType.java | 74 + .../common/enums/app/AuthorizationType.java | 85 + .../common/enums/app/InitLoginType.java | 85 + .../app/SamlAttributeStatementValueType.java | 104 + .../app/SamlEncryptAssertAlgorithmType.java | 64 + .../enums/app/SamlNameIdFormatType.java | 80 + .../common/enums/app/SamlNameIdValueType.java | 78 + .../enums/app/SamlParseMetadataType.java | 35 + .../app/SamlSignAssertAlgorithmType.java | 64 + .../app/SamlSignResponseAlgorithmType.java | 64 + .../converter/AppCertUsingTypeConverter.java | 69 + .../app/converter/AppProtocolConverter.java | 67 + .../enums/app/converter/AppTypeConverter.java | 67 + .../SamlAttributeNameFormatConverter.java | 48 + ...lAttributeStatementValueTypeConverter.java | 48 + .../SamlAuthnContextClassRefTypeConvert.java | 47 + ...SamlEncryptAssertAlgorithmTypeConvert.java | 49 + .../SamlNameIdFormatTypeConverter.java | 48 + .../converter/SamlNameIdTypeConverter.java | 45 + .../SamlSignAssertAlgorithmTypeConvert.java | 47 + .../SamlSignResponseAlgorithmTypeConvert.java | 47 + .../app/converter/SsoInitiatorConverter.java | 67 + .../app/converter/SsoScopeConverter.java | 67 + .../enums/converter/DataOriginConverter.java | 67 + .../IdentityProviderCategoryConverter.java | 68 + .../IdentityProviderTypeConverter.java | 68 + .../enums/converter/LanguageConverter.java | 69 + .../enums/converter/MailTypeConverter.java | 69 + .../converter/MessageCategoryConverter.java | 69 + .../enums/converter/MfaMannerConverter.java | 69 + .../enums/converter/MfaModeConverter.java | 69 + .../converter/OrganizationTypeConverter.java | 67 + .../converter/PermissionActionConverter.java | 69 + .../converter/PolicyEffectConverter.java | 67 + .../converter/PolicyObjectTypeConverter.java | 67 + .../converter/PolicySubjectConverter.java | 69 + .../enums/converter/SmsTypeConverter.java | 69 + .../enums/converter/SyncStatusConverter.java | 68 + .../enums/converter/TriggerTypeConverter.java | 66 + .../enums/converter/UserIdTypeConverter.java | 68 + .../enums/converter/UserStatusConverter.java | 67 + .../enums/converter/UserTypeConverter.java | 67 + .../common/enums/converter/package-info.java | 18 + .../IdentitySourceProvider.java | 94 + .../IdentitySourceProviderConverter.java | 70 + .../IdentitySourceActionType.java | 89 + .../IdentitySourceObjectType.java | 87 + .../IdentitySourceActionTypeConverter.java | 67 + .../IdentitySourceObjectTypeConverter.java | 67 + .../employee/common/enums/package-info.java | 18 + .../enums/setting/converter/package-info.java | 18 + .../common/enums/setting/package-info.java | 18 + .../BindMfaNotFoundSecretException.java | 34 + .../exception/InvalidMfaCodeException.java | 35 + .../LoginOtpActionNotSupportException.java | 32 + .../exception/MailMessageSendException.java | 58 + .../exception/MailProviderException.java | 56 + .../exception/MailTemplateException.java | 56 + .../exception/MessageSendException.java | 36 + .../common/exception/OtpSendException.java | 34 + .../PasswordValidatedFailException.java | 38 + .../exception/PrepareBindMfaException.java | 35 + .../exception/UserNotFoundException.java | 34 + .../app/AppAccountExistException.java | 33 + .../app/AppAccountNotExistException.java | 33 + .../app/AppPolicyNotExistException.java | 32 + .../app/AppResourceNotExistException.java | 32 + .../app/AppRoleNotExistException.java | 32 + .../common/exception/app/package-info.java | 18 + .../handler/GlobalExceptionHandler.java | 111 + .../employee/common/geo/GeoLocation.java | 107 + .../common/geo/GeoLocationProviderConfig.java | 59 + .../common/geo/GeoLocationService.java | 42 + .../geo/NoneGeoLocationServiceImpl.java | 46 + .../MaxmindGeoLocationServiceImpl.java | 259 + .../geo/maxmind/MaxmindProviderConfig.java | 40 + .../UpdateMaxmindTaskConfiguration.java | 61 + .../maxmind/enums/GeoLocationProvider.java | 96 + .../common/geo/maxmind/package-info.java | 18 + .../employee/common/geo/package-info.java | 24 + .../common/geo/taobao/package-info.java | 18 + .../common/message/SendMessageException.java | 105 + .../common/message/SmsProviderException.java | 94 + .../common/message/enums/MailProvider.java | 118 + .../common/message/enums/MailSafetyType.java | 86 + .../common/message/enums/MessageType.java | 87 + .../common/message/enums/SmsProvider.java | 85 + .../MailProviderPlatformConverter.java | 69 + .../convert/MailSafetyTypeConverter.java | 69 + .../enums/convert/MessageTypeConverter.java | 69 + .../enums/convert/SmsProviderConverter.java | 69 + .../mail/DefaultMailProviderSendImpl.java | 142 + .../message/mail/MailNoneProviderSend.java | 44 + .../message/mail/MailProviderConfig.java | 91 + .../common/message/mail/MailProviderSend.java | 49 + .../common/message/mail/SendMailRequest.java | 48 + .../common/message/mail/package-info.java | 18 + .../common/message/sms/SendSmsRequest.java | 47 + .../message/sms/SmsNoneProviderSend.java | 38 + .../common/message/sms/SmsProviderConfig.java | 54 + .../common/message/sms/SmsProviderSend.java | 51 + .../common/message/sms/SmsResponse.java | 50 + .../message/sms/SmsSendProviderFactory.java | 60 + .../sms/aliyun/AliyunSmsProviderConfig.java | 56 + .../sms/aliyun/AliyunSmsProviderSend.java | 84 + .../message/sms/aliyun/package-info.java | 18 + .../common/message/sms/package-info.java | 18 + .../sms/qiniu/QiNiuSmsProviderConfig.java | 49 + .../sms/qiniu/QiNiuSmsProviderSend.java | 69 + .../message/sms/qiniu/package-info.java | 18 + .../sms/tencent/TencentSmsProviderConfig.java | 68 + .../sms/tencent/TencentSmsProviderSend.java | 135 + .../message/sms/tencent/package-info.java | 18 + .../topiam/employee/common/package-info.java | 18 + .../repository/MailSendRecordRepository.java | 33 + .../repository/SmsSendRecordRepository.java | 31 + ...rganizationMemberCustomizedRepository.java | 38 + .../account/OrganizationMemberRepository.java | 77 + .../account/OrganizationRepository.java | 200 + .../OrganizationRepositoryCustomized.java | 43 + .../account/UserDetailRepository.java | 79 + .../UserDetailRepositoryCustomized.java | 45 + .../account/UserGroupMemberRepository.java | 78 + .../UserGroupMemberRepositoryCustomized.java | 39 + .../account/UserGroupRepository.java | 40 + .../UserHistoryPasswordRepository.java | 49 + .../repository/account/UserIdpRepository.java | 37 + .../account/UserIdpRepositoryCustomized.java | 58 + .../repository/account/UserRepository.java | 271 + .../account/UserRepositoryCustomized.java | 105 + ...izationMemberCustomizedRepositoryImpl.java | 73 + .../OrganizationRepositoryCustomizedImpl.java | 128 + .../UserDetailRepositoryCustomizedImpl.java | 120 + ...erGroupMemberRepositoryCustomizedImpl.java | 113 + .../impl/UserIdpRepositoryCustomizedImpl.java | 120 + .../impl/UserRepositoryCustomizedImpl.java | 355 + .../account/impl/mapper/UserEntityMapper.java | 83 + .../impl/mapper/UserIdpBindPoMapper.java | 89 + .../account/impl/mapper/UserPoMapper.java | 84 + .../app/AppAccessPolicyRepository.java | 57 + .../AppAccessPolicyRepositoryCustomized.java | 43 + .../repository/app/AppAccountRepository.java | 90 + .../app/AppAccountRepositoryCustomized.java | 43 + .../repository/app/AppCertRepository.java | 92 + .../app/AppOidcConfigRepository.java | 76 + .../AppOidcConfigRepositoryCustomized.java | 50 + .../app/AppPermissionActionRepository.java | 46 + .../app/AppPermissionPolicyRepository.java | 75 + ...pPermissionPolicyRepositoryCustomized.java | 39 + .../app/AppPermissionResourceRepository.java | 43 + .../app/AppPermissionRoleRepository.java | 55 + .../common/repository/app/AppRepository.java | 99 + .../app/AppRepositoryCustomized.java | 56 + .../app/AppSaml2ConfigRepository.java | 76 + .../AppSaml2ConfigRepositoryCustomized.java | 42 + ...pAccessPolicyRepositoryCustomizedImpl.java | 97 + .../AppAccountRepositoryCustomizedImpl.java | 98 + ...AppOidcConfigRepositoryCustomizedImpl.java | 95 + ...missionPolicyRepositoryCustomizedImpl.java | 130 + .../app/impl/AppRepositoryCustomizedImpl.java | 131 + ...ppSaml2ConfigRepositoryCustomizedImpl.java | 71 + .../ResourceRepositoryCustomizedImpl.java | 38 + .../impl/RoleRepositoryCustomizedImpl.java | 40 + .../impl/mapper/AppAccessPolicyPoMapper.java | 64 + .../app/impl/mapper/AppAccountPoMapper.java | 62 + .../app/impl/mapper/AppEntityMapper.java | 68 + .../impl/mapper/AppOidcConfigPoMapper.java | 91 + .../mapper/AppPermissionPolicyPoMapper.java | 62 + .../impl/mapper/AppSaml2ConfigPoMapper.java | 128 + .../common/repository/app/package-info.java | 18 + .../IdentityProviderRepository.java | 130 + .../ResourceRepositoryCustomized.java | 25 + .../RoleRepositoryCustomized.java | 26 + .../IdentitySourceEventRecordRepository.java | 37 + ...SourceEventRecordRepositoryCustomized.java | 37 + .../IdentitySourceRepository.java | 129 + .../IdentitySourceSyncHistoryRepository.java | 38 + .../IdentitySourceSyncRecordRepository.java | 37 + ...ySourceSyncRecordRepositoryCustomized.java | 37 + ...ceEventRecordRepositoryCustomizedImpl.java | 83 + ...rceSyncRecordRepositoryCustomizedImpl.java | 81 + .../common/repository/package-info.java | 18 + .../setting/AdministratorRepository.java | 133 + .../setting/MailTemplateRepository.java | 53 + .../repository/setting/SettingRepository.java | 106 + .../common/storage/AbstractStorage.java | 65 + .../employee/common/storage/Storage.java | 47 + .../common/storage/StorageConfig.java | 74 + .../common/storage/StorageFactory.java | 48 + .../storage/StorageProviderException.java | 56 + .../controller/StorageFileResource.java | 90 + .../common/storage/enums/StorageProvider.java | 107 + .../convert/StorageProviderConverter.java | 69 + .../common/storage/impl/AliYunOssStorage.java | 145 + .../common/storage/impl/LocalStorage.java | 59 + .../common/storage/impl/MinIoStorage.java | 134 + .../common/storage/impl/QiNiuKodoStorage.java | 155 + .../storage/impl/TencentCosStorage.java | 173 + .../employee/common/storage/package-info.java | 18 + .../util/InputStreamMetadataResolver.java | 75 + .../employee/common/util/RequestUtils.java | 84 + .../common/util/SamlKeyStoreProvider.java | 172 + .../employee/common/util/SamlUtils.java | 207 + .../employee/common/util/X509Utilities.java | 136 + .../employee/common/util/package-info.java | 18 + .../main/resources/config/logback-spring.xml | 169 + .../db/changelog/administrator-changelog.xml | 70 + .../resources/db/changelog/app-changelog.xml | 67 + .../changelog/app_access_policy-changelog.xml | 59 + .../db/changelog/app_account-changelog.xml | 55 + .../db/changelog/app_cert-changelog.xml | 60 + .../changelog/app_oidc_config-changelog.xml | 80 + .../app_permission_action-changelog.xml | 60 + .../app_permission_policy-changelog.xml | 68 + .../app_permission_resource-changelog.xml | 61 + .../app_permission_role-changelog.xml | 57 + .../changelog/app_saml2_config-changelog.xml | 79 + .../db/changelog/audit-changelog.xml | 60 + .../changelog/identity_provider-changelog.xml | 68 + .../changelog/identity_source-changelog.xml | 67 + ...identity_source_event_record-changelog.xml | 71 + ...identity_source_sync_history-changelog.xml | 80 + .../identity_source_sync_record-changelog.xml | 68 + .../changelog/mail_send_record-changelog.xml | 54 + .../db/changelog/mail_template-changelog.xml | 58 + .../db/changelog/organization-changelog.xml | 97 + .../organization_member-changelog.xml | 58 + .../db/changelog/setting-changlog.xml | 53 + .../changelog/sms_send_record-changelog.xml | 54 + .../resources/db/changelog/user-changelog.xml | 85 + .../db/changelog/user_detail-changelog.xml | 57 + .../db/changelog/user_group-changelog.xml | 53 + .../changelog/user_group_member-changelog.xml | 58 + .../user_history_password-changelog.xml | 50 + .../db/changelog/user_idp_bind-changlog.xml | 59 + .../resources/db/eiam-changelog-master.xml | 29 + .../dictionaries/10k-most-common.txt | 10000 ++++++++++++++++ .../content/again-verify-mail-content.html | 46 + .../mail/content/bind-mail-content.html | 47 + .../password-soon-expired-content.html | 46 + .../reset-password-confirm-content.html | 46 + .../mail/content/reset-password-content.html | 46 + .../content/update-bind-mail-content.html | 47 + .../mail/content/update-password-content.html | 46 + .../mail/content/verify-email-content.html | 54 + .../mail/content/warning-content.html | 47 + .../mail/content/welcome-mail-content.html | 46 + .../sms/template/sms-template_en.properties | 30 + .../sms/template/sms-template_zh.properties | 30 + eiam-console/Dockerfile | 48 + eiam-console/pom.xml | 131 + .../employee/EiamConsoleApplication.java | 36 + .../topiam/employee/ServletInitializer.java | 36 + .../ConsoleApiConfiguration.java | 140 + .../ConsoleFrontendConfiguration.java | 55 + .../ConsoleSecurityConfiguration.java | 307 + .../console/configuration/package-info.java | 24 + .../console/constants/package-info.java | 18 + .../controller/CurrentUserEndpoint.java | 131 + .../controller/FrontendForwardController.java | 41 + .../account/OrganizationController.java | 241 + .../controller/account/UserController.java | 381 + .../account/UserGroupController.java | 230 + .../account/UserIdpBindController.java | 42 + .../analysis/AnalysisController.java | 166 + .../controller/analysis/package-info.java | 18 + .../app/AppAccessPolicyController.java | 112 + .../controller/app/AppAccountController.java | 111 + .../controller/app/AppCertController.java | 69 + .../console/controller/app/AppController.java | 207 + .../app/AppPermissionActionController.java | 76 + .../app/AppPermissionPolicyController.java | 142 + .../app/AppPermissionResourceController.java | 213 + .../app/AppPermissionRoleController.java | 205 + .../controller/app/AppSaml2Controller.java | 105 + .../controller/app/AppTemplateController.java | 87 + .../IdentityProviderController.java | 178 + .../IdentitySourceController.java | 245 + .../IdentitySourceEventController.java | 74 + .../IdentitySourceSyncController.java | 110 + .../console/controller/package-info.java | 22 + .../session/SessionManageEndpoint.java | 245 + .../setting/AdministratorController.java | 221 + .../setting/GeoIpLibraryController.java | 121 + .../setting/MailProviderController.java | 140 + .../setting/MailTemplateController.java | 127 + .../setting/SecurityController.java | 214 + .../setting/SmsProviderController.java | 139 + .../setting/SmsTemplateController.java | 74 + .../controller/setting/StorageController.java | 105 + .../account/OrganizationConverter.java | 166 + .../converter/account/UserConverter.java | 344 + .../converter/account/UserGroupConverter.java | 171 + .../app/AppAccessPolicyConverter.java | 94 + .../converter/app/AppAccountConverter.java | 89 + .../converter/app/AppCertConverter.java | 81 + .../console/converter/app/AppConverter.java | 199 + .../app/AppPermissionActionConverter.java | 125 + .../app/AppPermissionPolicyConverter.java | 103 + .../app/AppPermissionResourceConverter.java | 140 + .../app/AppPermissionRoleConverter.java | 141 + .../converter/app/UserIdpBindConverter.java | 59 + .../IdentityProviderConverter.java | 265 + .../IdentitySourceConverter.java | 280 + .../IdentitySourceEventRecordConverter.java | 103 + .../IdentitySourceSyncConverter.java | 203 + .../console/converter/package-info.java | 22 + .../setting/AdministratorConverter.java | 150 + .../setting/GeoLocationSettingConverter.java | 119 + .../setting/MailTemplateConverter.java | 118 + .../setting/MessageSettingConverter.java | 237 + .../setting/PasswordPolicyConverter.java | 219 + .../setting/SecuritySettingConverter.java | 238 + .../setting/StorageSettingConverter.java | 142 + ...onsoleAdminPasswordInitializeListener.java | 167 + .../console/listener/package-info.java | 18 + .../topiam/employee/console/package-info.java | 18 + .../IdentitySourceConfigValidatorParam.java | 64 + .../pojo/other/OrganizationExcelData.java | 79 + .../console/pojo/other/package-info.java | 18 + .../employee/console/pojo/package-info.java | 24 + .../query/account/UserGroupListQuery.java | 54 + .../pojo/query/analysis/AnalysisQuery.java | 85 + .../console/pojo/query/app/AppCertQuery.java | 56 + .../app/AppPermissionActionListQuery.java | 65 + .../query/app/AppPermissionRoleListQuery.java | 67 + .../console/pojo/query/app/AppQuery.java | 53 + .../pojo/query/app/AppResourceListQuery.java | 61 + .../IdentityProviderListQuery.java | 59 + .../query/authentication/package-info.java | 18 + .../IdentitySourceEventRecordListQuery.java | 70 + .../identity/IdentitySourceListQuery.java | 48 + .../IdentitySourceSyncHistoryListQuery.java | 73 + .../IdentitySourceSyncRecordListQuery.java | 74 + .../console/pojo/query/package-info.java | 18 + .../query/setting/AdministratorListQuery.java | 40 + .../account/OrganizationChildResult.java | 106 + .../result/account/OrganizationResult.java | 81 + .../account/OrganizationRootResult.java | 106 + .../account/OrganizationTreeResult.java | 115 + .../result/account/UserGroupListResult.java | 64 + .../account/UserGroupMemberListResult.java | 80 + .../pojo/result/account/UserGroupResult.java | 61 + .../pojo/result/account/UserListResult.java | 130 + .../account/UserLoginAuditListResult.java | 74 + .../pojo/result/account/UserResult.java | 170 + .../result/analysis/AppVisitRankResult.java | 49 + .../analysis/AuthnHotProviderResult.java | 49 + .../result/analysis/AuthnQuantityResult.java | 55 + .../pojo/result/analysis/OverviewResult.java | 59 + .../result/app/AppAccessPolicyResult.java | 98 + .../pojo/result/app/AppAccountListResult.java | 95 + .../pojo/result/app/AppCertListResult.java | 73 + .../pojo/result/app/AppCreateResult.java | 44 + .../console/pojo/result/app/AppGetResult.java | 107 + .../pojo/result/app/AppListResult.java | 85 + .../app/AppPermissionActionListResult.java | 131 + .../app/AppPermissionPolicyGetResult.java | 87 + .../app/AppPermissionPolicyListResult.java | 84 + .../app/AppPermissionResourceGetResult.java | 107 + .../app/AppPermissionResourceListResult.java | 77 + .../app/AppPermissionRoleListResult.java | 75 + .../result/app/AppPermissionRoleResult.java | 69 + .../pojo/result/app/AppTemplateResult.java | 72 + .../result/app/ParseSaml2MetadataResult.java | 97 + .../result/app/UserIdpBindListResult.java | 67 + .../IdentityProviderCreateResult.java | 52 + .../IdentityProviderListResult.java | 74 + .../IdentityProviderResult.java | 84 + .../IdentitySourceConfigGetResult.java | 70 + .../IdentitySourceEventRecordListResult.java | 86 + .../IdentitySourceGetResult.java | 73 + .../IdentitySourceListResult.java | 77 + .../IdentitySourceSyncHistoryListResult.java | 122 + .../IdentitySourceSyncRecordListResult.java | 80 + .../console/pojo/result/package-info.java | 18 + .../setting/AdministratorListResult.java | 103 + .../result/setting/AdministratorResult.java | 97 + .../setting/EmailProviderConfigResult.java | 96 + .../setting/EmailTemplateListResult.java | 64 + .../result/setting/EmailTemplateResult.java | 74 + .../result/setting/GeoIpProviderResult.java | 63 + .../setting/PasswordPolicyConfigResult.java | 107 + .../setting/SecurityBasicConfigResult.java | 77 + .../setting/SecurityCaptchaConfigResult.java | 56 + .../setting/SecurityMfaConfigResult.java | 51 + .../result/setting/SmsTemplateListResult.java | 73 + .../setting/StorageProviderConfigResult.java | 61 + .../setting/WeakPasswordLibListResult.java | 43 + .../save/account/OrganizationCreateParam.java | 101 + .../pojo/save/account/UserCreateParam.java | 128 + .../save/account/UserGroupCreateParam.java | 59 + .../save/app/AppAccessPolicyCreateParam.java | 60 + .../pojo/save/app/AppAccountCreateParam.java | 57 + .../console/pojo/save/app/AppCreateParam.java | 58 + .../app/AppPermissionPolicyCreateParam.java | 84 + .../app/AppPermissionResourceCreateParam.java | 76 + .../app/AppPermissionRoleCreateParam.java | 62 + .../save/app/AppPermissionsActionParam.java | 59 + .../pojo/save/app/AppSaveConfigParam.java | 52 + .../IdentityProviderCreateParam.java | 87 + .../InitializeAdminSaveParam.java | 68 + .../IdentitySourceConfigSaveParam.java | 76 + .../IdentitySourceCreateParam.java | 65 + .../IdentitySourceCreateResult.java | 43 + .../setting/AdministratorCreateParam.java | 69 + .../setting/EmailCustomTemplateSaveParam.java | 61 + .../save/setting/GeoIpProviderSaveParam.java | 56 + .../save/setting/MailProviderSaveParam.java | 79 + .../save/setting/PasswordPolicySaveParam.java | 108 + .../save/setting/SecurityBasicSaveParam.java | 77 + .../setting/SecurityCaptchaSaveParam.java | 55 + .../save/setting/SecurityMfaSaveParam.java | 55 + .../save/setting/SmsProviderSaveParam.java | 75 + .../save/setting/StorageConfigSaveParam.java | 56 + .../pojo/setting/SmsProviderConfigResult.java | 79 + .../account/OrganizationUpdateParam.java | 82 + .../update/account/ResetPasswordParam.java | 81 + .../update/account/UserGroupUpdateParam.java | 65 + .../pojo/update/account/UserUpdateParam.java | 111 + .../app/AppPermissionPolicyUpdateParam.java | 89 + .../app/AppPermissionResourceUpdateParam.java | 92 + .../pojo/update/app/AppSaveConfigParam.java | 59 + .../pojo/update/app/AppUpdateParam.java | 62 + .../update/app/PermissionRoleUpdateParam.java | 62 + .../update/authentication/IdpUpdateParam.java | 84 + .../identity/IdentitySourceUpdateParam.java | 60 + .../setting/AdministratorUpdateParam.java | 71 + .../handler/ConsoleAccessDeniedHandler.java | 65 + .../ConsoleAuthenticationEntryPoint.java | 72 + .../ConsoleAuthenticationFailureHandler.java | 60 + .../ConsoleAuthenticationSuccessHandler.java | 60 + .../handler/ConsoleLogoutSuccessHandler.java | 67 + .../security/handler/package-info.java | 24 + ...oleAuthenticationFailureEventListener.java | 95 + ...oleAuthenticationSuccessEventListener.java | 91 + .../ConsoleLogoutSuccessEventListener.java | 58 + ...soleSessionInformationExpiredStrategy.java | 48 + .../console/security/package-info.java | 18 + .../service/account/OrganizationService.java | 149 + .../account/UserAccountAssociateService.java | 26 + .../service/account/UserGroupService.java | 116 + .../console/service/account/UserService.java | 172 + .../account/UserSocialBindService.java | 28 + .../account/impl/OrganizationServiceImpl.java | 408 + .../impl/UserAccountAssociateServiceImpl.java | 33 + .../account/impl/UserGroupServiceImpl.java | 235 + .../service/account/impl/UserServiceImpl.java | 604 + .../impl/UserSocialBindServiceImpl.java | 34 + .../userdetail/UserDetailsServiceImpl.java | 117 + .../service/analysis/AnalysisService.java | 57 + .../analysis/impl/AnalysisServiceImpl.java | 250 + .../service/analysis/package-info.java | 18 + .../service/app/AppAccessPolicyService.java | 59 + .../service/app/AppAccountService.java | 58 + .../console/service/app/AppCertService.java | 39 + .../app/AppPermissionActionService.java | 43 + .../app/AppPermissionPolicyService.java | 78 + .../app/AppPermissionResourceService.java | 99 + .../service/app/AppPermissionRoleService.java | 100 + .../console/service/app/AppSaml2Service.java | 55 + .../console/service/app/AppService.java | 114 + .../service/app/AppTemplateService.java | 49 + .../service/app/UserIdpBindService.java | 46 + .../app/impl/AppAccessPolicyServiceImpl.java | 152 + .../app/impl/AppAccountServiceImpl.java | 129 + .../service/app/impl/AppCertServiceImpl.java | 59 + .../impl/AppPermissionActionServiceImpl.java | 65 + .../impl/AppPermissionPolicyServiceImpl.java | 128 + .../AppPermissionResourceServiceImpl.java | 283 + .../impl/AppPermissionRoleServiceImpl.java | 220 + .../service/app/impl/AppSaml2ServiceImpl.java | 220 + .../service/app/impl/AppServiceImpl.java | 257 + .../app/impl/AppTemplateServiceImpl.java | 92 + .../app/impl/UserIdpBindServiceImpl.java | 90 + .../console/service/app/package-info.java | 18 + .../IdentityProviderService.java | 108 + .../impl/IdentityProviderServiceImpl.java | 213 + .../IdentitySourceEventRecordService.java | 44 + .../identitysource/IdentitySourceService.java | 124 + .../IdentitySourceSyncService.java | 60 + .../IdentitySourceEventRecordServiceImpl.java | 82 + .../impl/IdentitySourceServiceImpl.java | 278 + .../impl/IdentitySourceSyncServiceImpl.java | 148 + .../console/service/package-info.java | 22 + .../service/setting/AdministratorService.java | 106 + .../setting/GeoLocationSettingService.java | 59 + .../service/setting/MailTemplateService.java | 75 + .../setting/MessageSettingService.java | 75 + .../setting/PasswordPolicyService.java | 58 + .../setting/SecuritySettingService.java | 89 + .../service/setting/SettingService.java | 60 + .../service/setting/SmsTemplateService.java | 42 + .../setting/StorageSettingService.java | 51 + .../impl/AdministratorServiceImpl.java | 318 + .../impl/GeoLocationSettingServiceImpl.java | 105 + .../setting/impl/MailTemplateServiceImpl.java | 173 + .../impl/MessageSettingServiceImpl.java | 128 + .../impl/PasswordPolicyServiceImpl.java | 120 + .../impl/SecuritySettingServiceImpl.java | 203 + .../setting/impl/SettingServiceImpl.java | 100 + .../setting/impl/SmsTemplateServiceImpl.java | 74 + .../impl/StorageSettingServiceImpl.java | 86 + .../src/main/resources/application.yml | 162 + .../main/resources/fe/1098.d7361839.async.js | 18 + .../main/resources/fe/1158.74b40769.async.js | 18 + .../main/resources/fe/137.d372bd41.async.js | 18 + .../main/resources/fe/1576.0ddabdb9.async.js | 18 + .../main/resources/fe/1687.d91fbb33.async.js | 25 + .../main/resources/fe/1914.479e05f1.async.js | 126 + .../main/resources/fe/211.eb7863f3.async.js | 8947 ++++++++++++++ .../main/resources/fe/2172.0a9d7ed7.async.js | 18 + .../main/resources/fe/2181.f36b14fa.async.js | 18 + .../main/resources/fe/2387.e263de85.async.js | 18 + .../main/resources/fe/2406.b269972c.async.js | 18 + .../main/resources/fe/2453.55350af8.async.js | 33 + .../main/resources/fe/2657.26d96d2a.async.js | 99 + .../main/resources/fe/3956.63df22b1.async.js | 48 + .../main/resources/fe/4393.9e9cdbf7.async.js | 27 + .../main/resources/fe/4674.f866949b.async.js | 125 + .../main/resources/fe/4968.8584392a.async.js | 18 + .../main/resources/fe/5175.b7e3d2b9.async.js | 45 + .../main/resources/fe/5642.0270a7ef.async.js | 32 + .../main/resources/fe/6162.d72b1f25.async.js | 18 + .../main/resources/fe/6495.cbafa057.async.js | 21 + .../main/resources/fe/7468.1b93dfe0.async.js | 37 + .../main/resources/fe/7565.e8ec7e12.async.js | 20 + .../main/resources/fe/7640.94326ec9.async.js | 18 + .../main/resources/fe/7785.bdf19941.async.js | 18 + .../main/resources/fe/7980.263c2c95.async.js | 43 + .../main/resources/fe/8138.96849e31.async.js | 18 + .../main/resources/fe/8345.b475777a.async.js | 20 + .../main/resources/fe/918.adb4d8ee.async.js | 18 + .../main/resources/fe/9299.8ec110c6.async.js | 20 + .../main/resources/fe/9400.7f60eb97.async.js | 18 + .../main/resources/fe/9825.35d12015.async.js | 90 + .../src/main/resources/fe/ade5b70f.jpg | Bin 0 -> 152264 bytes .../src/main/resources/fe/favicon.ico | Bin 0 -> 1207 bytes .../src/main/resources/fe/full-logo-white.svg | 30 + .../src/main/resources/fe/full-logo.svg | 30 + eiam-console/src/main/resources/fe/index.html | 33 + .../main/resources/fe/login-background.png | Bin 0 -> 1168067 bytes eiam-console/src/main/resources/fe/logo.svg | 29 + .../resources/fe/p__404.11bb088a.async.js | 18 + .../resources/fe/p__Welcome.3630b9f4.async.js | 18 + ...ntitySourceDetail__index.28baad45.async.js | 18 + ...dentitySourceList__index.4fdd0185.async.js | 18 + .../p__account__List__index.04f77279.async.js | 22 + ...count__UserDetail__index.6d202951.async.js | 18 + ...__UserGroupDetail__index.28afa610.async.js | 18 + ...nt__UserGroupList__index.cf804be6.async.js | 19 + ...p__app__AppConfig__index.22328e6d.async.js | 18 + ...p__app__AppCreate__index.ec7a4eb2.async.js | 18 + .../p__app__AppList__index.3ad30674.async.js | 18 + .../fe/p__audit__index.8a224db1.async.js | 18 + ...ntityProviderList__index.8a915105.async.js | 18 + ...shboard__Analysis__index.35de0d16.async.js | 18 + ...ther__SessionList__index.2df68fa1.async.js | 18 + ...ng__Administrator__index.20ad7016.async.js | 18 + ...__setting__Basic__index.7c7fbc98.chunk.css | 18 + ...p__setting__Basic__index.7fc2a2e3.async.js | 18 + ...setting__Security__index.4e05945a.async.js | 18 + .../p__user__Login__index.2a52cc88.async.js | 18 + ...r__SessionExpired__index.6638a85c.async.js | 18 + ...t__plugin-layout__Layout.331cc1dd.async.js | 18 + ...__plugin-layout__Layout.74b4118c.chunk.css | 18 + .../src/main/resources/fe/umi.8f1943c2.js | 477 + .../src/main/resources/fe/umi.d85528d9.css | 18 + .../src/main/resources/fe/x4v0w8nb.png | Bin 0 -> 125946 bytes .../employee/GeoLocationServiceTest.java | 50 + .../java/cn/topiam/employee/package-info.java | 18 + eiam-core/pom.xml | 42 + .../configuration/EiamApiConfiguration.java | 72 + .../configuration/EiamAsyncConfiguration.java | 71 + .../configuration/EiamCacheConfiguration.java | 125 + .../EiamCaptchaValidatorConfiguration.java | 71 + .../configuration/EiamErrorConfiguration.java | 60 + .../EiamGeoLocationConfiguration.java | 82 + .../EiamJacksonConfiguration.java | 56 + .../EiamLiquibaseConfiguration.java | 56 + .../configuration/EiamMdcConfiguration.java | 45 + .../EiamMessageSendConfiguration.java | 89 + .../EiamPasswordConfiguration.java | 89 + .../EiamPreviewEnvConfiguration.java | 56 + .../EiamRepositoryConfiguration.java | 65 + .../EiamRestTemplateConfiguration.java | 45 + .../configuration/EiamRetryConfiguration.java | 32 + .../EiamSchedulingConfiguration.java | 53 + .../EiamStorageConfiguration.java | 79 + .../configuration/EiamSupportProperties.java | 139 + .../EiamValidationConfiguration.java | 63 + .../configuration/EiamWebConfiguration.java | 56 + .../core/configuration/package-info.java | 24 + .../core/context/ServerContextHelp.java | 114 + .../core/context/SettingContextHelp.java | 215 + .../employee/core/context/package-info.java | 18 + .../core/endpoint/CountryEndpoint.java | 45 + .../employee/core/endpoint/package-info.java | 18 + .../CurrentSessionStatusEndpoint.java | 118 + .../security/PasswordGenerateEndpoint.java | 65 + .../security/PublicSecretEndpoint.java | 99 + .../core/license/EiamCopyrightLicense.java | 62 + .../EiamSupportConfigurationListener.java | 88 + .../employee/core/license/package-info.java | 18 + .../core/listener/EiamReadyEventListener.java | 82 + .../employee/core/logger/LogAspect.java | 208 + .../employee/core/logger/package-info.java | 24 + .../employee/core/message/MsgVariable.java | 66 + .../core/message/mail/MailMsgEvent.java | 55 + .../message/mail/MailMsgEventListener.java | 194 + .../message/mail/MailMsgEventPublish.java | 87 + .../employee/core/message/mail/MailUtils.java | 61 + .../core/message/sms/SmsMsgEvent.java | 50 + .../core/message/sms/SmsMsgEventListener.java | 117 + .../core/message/sms/SmsMsgEventPublish.java | 112 + .../cn/topiam/employee/core/package-info.java | 18 + .../core/protocol/Saml2ProtocolConfig.java | 162 + .../employee/core/protocol/Saml2SsoModel.java | 179 + .../employee/core/protocol/package-info.java | 18 + .../security/SpringSecurityAuditorAware.java | 45 + .../AuthenticationTrustResolverImpl.java | 49 + .../authentication/IdpAuthentication.java | 143 + .../IdpAuthorizationManager.java | 47 + .../authentication/SmsAuthentication.java | 114 + .../core/security/authorization/Roles.java | 40 + .../captcha/CaptchaProviderConfig.java | 51 + .../security/captcha/CaptchaValidator.java | 37 + .../captcha/CaptchaValidatorFilter.java | 120 + .../security/captcha/NoneCaptchaProvider.java | 42 + .../geetest/GeeTestCaptchaProviderConfig.java | 51 + .../geetest/GeeTestCaptchaValidator.java | 120 + .../captcha/geetest/package-info.java | 18 + .../core/security/captcha/package-info.java | 18 + .../DefaultSecurityContextAccessor.java | 124 + .../context/SecurityContextAccessor.java | 99 + .../security/decrypt/DecryptException.java | 38 + .../security/decrypt/DecryptRequestBody.java | 34 + .../decrypt/DecryptRequestBodyAdvice.java | 110 + .../EncryptSecretNotExistException.java | 35 + .../security/form/FormLoginSecretFilter.java | 95 + .../security/form/GetCaptchaException.java | 33 + .../core/security/form/package-info.java | 18 + .../security/jackson2/CoreJackson2Module.java | 49 + .../IdpAuthenticationTokenDeserializer.java | 106 + .../jackson2/IdpAuthenticationTokenMixin.java | 36 + .../MfaAuthenticationTokenDeserializer.java | 67 + .../jackson2/MfaAuthenticationTokenMixin.java | 36 + .../jackson2/SavedRedirectDeserializer.java | 55 + .../security/jackson2/SavedRedirectMixin.java | 36 + .../SmsAuthenticationTokenDeserializer.java | 87 + .../jackson2/SmsAuthenticationTokenMixin.java | 36 + .../jackson2/UserDetailsDeserializer.java | 109 + .../security/jackson2/UserDetailsMixin.java | 36 + .../core/security/jump/JumpController.java | 84 + .../core/security/jump/package-info.java | 18 + .../core/security/mfa/MfaAuthentication.java | 90 + .../security/mfa/MfaAuthorizationManager.java | 47 + .../security/mfa/MfaProviderValidator.java | 33 + .../exception/InvalidMfaCodeException.java | 36 + .../InvalidMfaProviderConfigException.java | 30 + .../InvalidMfaProviderException.java | 40 + .../exception/MfaAlreadyExistsException.java | 35 + ...faProviderUpdateIsNotAllowedException.java | 30 + .../mfa/exception/MfaRequiredException.java | 32 + .../exception/MissingMfaCodeException.java | 32 + .../UnableToPersistMfaException.java | 28 + .../UnableToRetrieveMfaException.java | 30 + .../UserMfaConfigAlreadyExistsException.java | 30 + .../UserMfaConfigDoesNotExistException.java | 30 + .../mfa/provider/TotpAuthenticator.java | 155 + .../security/mfa/provider/package-info.java | 18 + .../core/security/otp/OtpContextHelp.java | 220 + .../core/security/otp/package-info.java | 18 + .../employee/core/security/package-info.java | 18 + .../security/password/PasswordGenerator.java | 33 + .../password/PasswordPolicyManager.java | 37 + .../security/password/PasswordValidator.java | 36 + .../enums/CipherComplexityConverter.java | 68 + .../enums/PasswordComplexityRule.java | 102 + .../exception/HistoryPasswordException.java | 49 + .../exception/InvalidPasswordException.java | 52 + .../PasswordComplexityRuleException.java | 49 + .../PasswordContinuousSameCharException.java | 50 + .../PasswordIllegalSequenceException.java | 49 + .../PasswordIncludeUserInfoException.java | 47 + .../exception/PasswordLengthException.java | 49 + .../exception/WeakPasswordException.java | 49 + .../password/exception/package-info.java | 18 + .../generator/DefaultPasswordGenerator.java | 62 + .../manager/DefaultPasswordPolicyManager.java | 262 + .../core/security/password/package-info.java | 18 + .../password/task/PasswordExpireTask.java | 31 + .../task/impl/PasswordExpireLockTask.java | 105 + .../task/impl/PasswordExpireWarnTask.java | 153 + .../security/password/task/package-info.java | 18 + .../validator/HistoryPasswordValidator.java | 80 + .../PasswordComplexityRuleValidator.java | 124 + .../PasswordContinuousSameCharValidator.java | 61 + .../PasswordIllegalSequenceValidator.java | 69 + .../PasswordIncludeUserInfoValidator.java | 151 + .../validator/PasswordLengthValidator.java | 60 + .../validator/WeakPasswordValidator.java | 79 + .../weak/DefaultPasswordWeakLibImpl.java | 125 + .../password/weak/PasswordWeakLib.java | 43 + .../security/password/weak/package-info.java | 18 + .../HttpSessionRedirectCache.java | 141 + .../LoginRedirectParameterFilter.java | 70 + .../security/savedredirect/RedirectCache.java | 71 + .../security/savedredirect/SavedRedirect.java | 73 + .../security/savedredirect/package-info.java | 18 + .../core/security/session/SessionDetails.java | 87 + .../TopIamSessionBackedSessionRegistry.java | 195 + .../session/TopIamSessionConfiguration.java | 134 + .../security/task/UserExpireLockTask.java | 95 + .../core/security/task/UserUnlockTask.java | 83 + .../security/userdetails/UserDetails.java | 149 + .../userdetails/UserDetailsService.java | 29 + .../employee/core/security/util/AesUtils.java | 190 + .../core/security/util/SecurityUtils.java | 200 + .../core/security/util/UserUtils.java | 71 + .../core/security/util/ValidateCodeUtils.java | 72 + .../core/security/util/package-info.java | 23 + .../constant/GeoIpProviderConstants.java | 32 + .../constant/MessageSettingConstants.java | 81 + .../setting/constant/MfaSettingConstants.java | 71 + .../PasswordPolicySettingConstants.java | 167 + .../constant/SecuritySettingConstants.java | 118 + .../StorageProviderSettingConstants.java | 41 + .../employee/core/setting/package-info.java | 18 + .../META-INF/resources/static/favicon.ico | Bin 0 -> 1207 bytes .../main/resources/META-INF/spring.factories | 6 + ...ot.autoconfigure.AutoConfiguration.imports | 0 .../resources/templates/jump/jump_get.ftlh | 221 + .../resources/templates/jump/jump_post.ftlh | 212 + eiam-identity-source/README.md | 13 + .../eiam-identity-source-ad/pom.xml | 35 + .../eiam-identity-source-all/pom.xml | 78 + .../eiam-identity-source-core/pom.xml | 32 + .../core/AbstractDefaultIdentitySource.java | 144 + .../identitysource/core/IdentitySource.java | 71 + .../core/IdentitySourceConfig.java | 30 + .../core/IdentitySourceConfigValidator.java | 34 + .../client/AbstractIdentitySourceClient.java | 47 + .../core/client/IdentitySourceClient.java | 93 + .../core/client/package-info.java | 24 + .../identitysource/core/domain/Dept.java | 66 + .../identitysource/core/domain/User.java | 82 + .../core/domain/UserDetail.java | 44 + .../core/domain/package-info.java | 18 + .../enums/IdentitySourceEventReceiveType.java | 57 + .../core/event/IdentitySourceEvent.java | 45 + .../event/IdentitySourceEventListener.java | 45 + .../IdentitySourceEventListenerRunner.java | 118 + .../core/event/IdentitySourceEventType.java | 40 + .../core/event/IdentitySourceEventUtils.java | 67 + .../core/exception/ApiCallException.java | 57 + .../IdentitySourceNotExistException.java | 32 + .../InvalidClientConfigException.java | 36 + .../core/exception/package-info.java | 24 + .../identitysource/core/package-info.java | 22 + .../IdentitySourceEventPostProcessor.java | 35 + .../IdentitySourceSyncDeptPostProcessor.java | 45 + .../IdentitySourceSyncUserPostProcessor.java | 45 + .../modal/IdentitySourceEventProcessData.java | 61 + .../core/processor/modal/package-info.java | 18 + .../core/processor/package-info.java | 18 + .../eiam-identity-source-dingtalk/pom.xml | 39 + .../dingtalk/DingTalkConfig.java | 65 + .../dingtalk/DingTalkConfigValidator.java | 104 + .../dingtalk/DingTalkConstants.java | 72 + .../dingtalk/DingTalkIdentitySource.java | 214 + .../client/AbstractDingTalkClient.java | 104 + .../dingtalk/client/DingTalkClient.java | 294 + .../dingtalk/client/package-info.java | 18 + .../dingtalk/enums/DingTalkEventType.java | 97 + .../identitysource/dingtalk/package-info.java | 18 + .../util/DingTalkEventCryptoUtils.java | 434 + .../eiam-identity-source-feishu/pom.xml | 42 + .../identitysource/feishu/FeiShuConfig.java | 61 + .../feishu/FeiShuConfigValidator.java | 91 + .../identitysource/feishu/FeiShuConstant.java | 82 + .../feishu/FieShuIdentitySource.java | 153 + .../feishu/client/AbstractFeiShuClient.java | 114 + .../feishu/client/FeiShuClient.java | 260 + .../feishu/client/package-info.java | 18 + .../feishu/domain/BaseRequest.java | 57 + .../feishu/domain/BaseResponse.java | 42 + .../feishu/domain/package-info.java | 18 + .../domain/request/GetAccessTokenRequest.java | 46 + .../domain/request/GetDeptListRequest.java | 59 + .../domain/request/GetUserListRequest.java | 59 + .../response/GetAccessTokenResponse.java | 48 + .../response/GetDepartmentResponse.java | 152 + .../domain/response/GetDetailsResponse.java | 45 + .../domain/response/GetListResponse.java | 62 + .../domain/response/GetUserResponse.java | 212 + .../feishu/enums/FeiShuEventType.java | 93 + .../feishu/enums/package-info.java | 18 + .../feishu/util/FeiShuEventDecryptUtils.java | 63 + .../eiam-identity-source-ldap/pom.xml | 35 + .../eiam-identity-source-wechatwork/pom.xml | 43 + .../wechatwork/WeChatWorkConfig.java | 63 + .../wechatwork/WeChatWorkConfigValidator.java | 78 + .../wechatwork/WeChatWorkConstant.java | 72 + .../wechatwork/WeChatWorkIdentitySource.java | 281 + .../client/AbstractWeChatWorkClient.java | 99 + .../wechatwork/client/WeChatWorkClient.java | 177 + .../wechatwork/client/package-info.java | 18 + .../wechatwork/domain/package-info.java | 18 + .../domain/response/AccessTokenResponse.java | 38 + .../domain/response/BaseResponse.java | 50 + .../domain/response/Department.java | 37 + .../domain/response/GetDeptListResponse.java | 35 + .../domain/response/GetDeptResponse.java | 33 + .../domain/response/GetUserListResponse.java | 110 + .../domain/response/GetUserResponse.java | 101 + .../wechatwork/enums/WeChatWorkEventType.java | 89 + .../wechatwork/enums/package-info.java | 18 + .../wechatwork/package-info.java | 18 + .../wechatwork/util/AesException.java | 72 + .../wechatwork/util/ByteGroup.java | 48 + .../wechatwork/util/Pkcs7Encoder.java | 76 + .../identitysource/wechatwork/util/Sha1.java | 72 + .../wechatwork/util/WxBizMsgCrypt.java | 305 + .../wechatwork/util/XmlParse.java | 115 + .../wechatwork/util/package-info.java | 18 + .../eiam-identity-source-welink/pom.xml | 35 + eiam-identity-source/pom.xml | 54 + eiam-openapi/Dockerfile | 48 + eiam-openapi/pom.xml | 62 + .../employee/EiamOpenApiApplication.java | 38 + .../topiam/employee/ServletInitializer.java | 36 + .../configuration/OpenApiConfiguration.java | 88 + .../OpenApiSecurityConfiguration.java | 48 + .../openapi/configuration/package-info.java | 18 + .../openapi/constants/OpenApiConstants.java | 44 + .../openapi/constants/package-info.java | 18 + .../converter/app/AppAccountConverter.java | 89 + .../app/AppPermissionActionConverter.java | 37 + .../app/AppPermissionPolicyConverter.java | 89 + .../app/AppPermissionResourceConverter.java | 141 + .../app/AppPermissionRoleConverter.java | 141 + .../openapi/converter/package-info.java | 18 + .../openapi/endpoint/package-info.java | 18 + .../AppPermissionPolicyEndpoint.java | 63 + .../AppPermissionResourceEndpoint.java | 72 + .../permission/AppPermissionRoleEndpoint.java | 72 + .../endpoint/permission/package-info.java | 18 + .../openapi/exception/package-info.java | 18 + .../topiam/employee/openapi/package-info.java | 18 + .../employee/openapi/pojo/package-info.java | 18 + .../request/app/AppAccountCreateParam.java | 57 + .../app/AppPermissionActionCreateParam.java | 78 + .../request/app/AppPermissionListQuery.java | 61 + .../app/AppPermissionPolicyCreateParam.java | 84 + .../app/AppPermissionPolicyUpdateParam.java | 89 + .../app/AppPermissionResourceCreateParam.java | 76 + .../app/AppPermissionResourceUpdateParam.java | 68 + .../app/AppPermissionRoleCreateParam.java | 62 + .../app/AppPermissionRoleListQuery.java | 58 + .../app/AppPermissionsActionParam.java | 59 + .../request/app/AppResourceListQuery.java | 51 + .../pojo/request/app/OpenApiPolicyQuery.java | 76 + .../app/PermissionRoleUpdateParam.java | 62 + .../app/ResourceActionUpdateParam.java | 85 + .../openapi/pojo/request/package-info.java | 18 + .../response/app/AppAccountListResult.java | 95 + .../app/AppPermissionActionGetResult.java | 66 + .../app/AppPermissionActionListResult.java | 72 + .../app/AppPermissionPolicyGetResult.java | 87 + .../app/AppPermissionResourceGetResult.java | 107 + .../app/AppPermissionResourceListResult.java | 71 + .../app/AppPermissionRoleListResult.java | 75 + .../response/app/AppPermissionRoleResult.java | 69 + .../openapi/pojo/response/package-info.java | 18 + .../openapi/service/AppAccountService.java | 58 + .../service/AppPermissionActionService.java | 76 + .../service/AppPermissionPolicyService.java | 77 + .../service/AppPermissionResourceService.java | 90 + .../service/AppPermissionRoleService.java | 100 + .../service/impl/AppAccountServiceImpl.java | 126 + .../impl/AppPermissionActionServiceImpl.java | 99 + .../impl/AppPermissionPolicyServiceImpl.java | 136 + .../AppPermissionResourceServiceImpl.java | 240 + .../impl/AppPermissionRoleServiceImpl.java | 211 + .../openapi/service/impl/package-info.java | 18 + .../openapi/service/package-info.java | 18 + .../src/main/resources/application.yml | 162 + eiam-portal/Dockerfile | 49 + eiam-portal/pom.xml | 114 + .../employee/EiamPortalApplication.java | 38 + .../topiam/employee/ServletInitializer.java | 36 + .../configuration/PortalApiConfiguration.java | 94 + .../PortalFrontendConfiguration.java | 55 + .../PortalSecurityConfiguration.java | 693 ++ .../portal/configuration/package-info.java | 18 + .../portal/constant/PortalConstants.java | 63 + .../portal/controller/AccountController.java | 127 + .../portal/controller/AppController.java | 66 + .../controller/CurrentUserController.java | 157 + .../controller/FrontendForwardController.java | 41 + .../portal/controller/NoticeController.java | 32 + .../controller/SessionManageEndpoint.java | 240 + .../login/LoginConfigController.java | 60 + .../controller/login/LoginOtpController.java | 173 + .../portal/controller/package-info.java | 18 + .../portal/converter/AccountConverter.java | 120 + .../portal/converter/AppConverter.java | 136 + .../converter/LoginConfigConverter.java | 55 + .../portal/converter/package-info.java | 18 + .../handler/PortalAccessDeniedHandler.java | 65 + .../PortalAuthenticationEntryPoint.java | 89 + .../PortalAuthenticationFailureHandler.java | 119 + .../handler/PortalAuthenticationHandler.java | 171 + .../PortalAuthenticationSuccessHandler.java | 111 + .../handler/PortalLogoutSuccessHandler.java | 67 + .../employee/portal/handler/package-info.java | 24 + .../idp/IdpRedirectParameterMatcher.java | 63 + .../idp/bind/IdpAuthenticationConfigurer.java | 91 + .../bind/IdpBindUserAuthenticationFilter.java | 231 + .../portal/idp/bind/UserBindIdpException.java | 37 + .../portal/idp/bind/package-info.java | 18 + .../employee/portal/idp/package-info.java | 18 + ...talAuthenticationFailureEventListener.java | 128 + ...talAuthenticationSuccessEventListener.java | 97 + .../PortalLogoutSuccessEventListener.java | 58 + ...rtalSessionInformationExpiredStrategy.java | 48 + .../portal/mfa/EmailProviderValidator.java | 38 + .../mfa/MfaAuthenticationConfigurer.java | 75 + .../portal/mfa/MfaAuthenticationFilter.java | 143 + .../portal/mfa/SmsProviderValidator.java | 38 + .../mfa/email/EmailOtpProviderValidator.java | 38 + .../mfa/endpoint/MfaFactorsEndpoint.java | 86 + .../employee/portal/mfa/package-info.java | 18 + .../mfa/sms/SmsOtpProviderValidator.java | 38 + .../mfa/totp/TotpProviderValidator.java | 43 + .../topiam/employee/portal/package-info.java | 24 + .../employee/portal/pojo/package-info.java | 18 + .../portal/pojo/query/GetAppListQuery.java | 47 + .../pojo/request/AccountBindIdpRequest.java | 53 + .../portal/pojo/request/BindTotpRequest.java | 48 + .../pojo/request/ChangeEmailRequest.java | 50 + .../pojo/request/ChangePasswordRequest.java | 57 + .../pojo/request/ChangePhoneRequest.java | 50 + .../pojo/request/PrepareBindTotpRequest.java | 50 + .../pojo/request/UpdateUserInfoRequest.java | 56 + .../portal/pojo/result/GetAppListResult.java | 101 + .../portal/pojo/result/LoginConfigResult.java | 101 + .../pojo/result/LoginMfaFactorResult.java | 52 + .../pojo/result/PrepareBindMfaResult.java | 48 + .../portal/service/AccountService.java | 85 + .../employee/portal/service/AppService.java | 40 + .../portal/service/LoginConfigService.java | 36 + .../service/impl/AccountServiceImpl.java | 295 + .../portal/service/impl/AppServiceImpl.java | 65 + .../service/impl/LoginConfigServiceImpl.java | 88 + .../service/impl/UserIdpServiceImpl.java | 173 + .../portal/service/impl/package-info.java | 18 + .../employee/portal/service/package-info.java | 18 + .../userdetail/UserDetailsServiceImpl.java | 138 + .../src/main/resources/application.yml | 164 + .../main/resources/fe/155.9783c162.async.js | 48 + .../main/resources/fe/158.2475b980.async.js | 18 + .../main/resources/fe/181.6b6b0c27.async.js | 18 + .../main/resources/fe/393.0e12e6bb.async.js | 27 + .../main/resources/fe/400.229afeb0.async.js | 45 + .../main/resources/fe/423.b7e78da7.async.js | 173 + .../main/resources/fe/501.72622fa4.async.js | 21 + .../main/resources/fe/565.776b8811.async.js | 20 + .../main/resources/fe/575.ae828185.async.js | 33 + .../main/resources/fe/576.9918df2e.async.js | 18 + .../main/resources/fe/687.227b8843.async.js | 25 + .../main/resources/fe/738.1c5a5bbb.async.js | 246 + .../main/resources/fe/767.e87b9d25.async.js | 87 + .../main/resources/fe/847.f684e638.async.js | 75 + .../main/resources/fe/891.107bbd17.async.js | 18 + .../main/resources/fe/894.c90ee1f4.async.js | 18 + .../src/main/resources/fe/ade5b70f.jpg | Bin 0 -> 152264 bytes eiam-portal/src/main/resources/fe/favicon.ico | Bin 0 -> 1207 bytes .../src/main/resources/fe/full-logo-white.svg | 30 + .../src/main/resources/fe/full-logo.svg | 30 + eiam-portal/src/main/resources/fe/index.html | 33 + .../main/resources/fe/login-background.png | Bin 0 -> 174555 bytes eiam-portal/src/main/resources/fe/logo.svg | 29 + .../resources/fe/p__404.e855b3a1.async.js | 18 + .../fe/p__Account__index.5d34b694.async.js | 18 + .../fe/p__Account__index.c0a83ac3.chunk.css | 18 + .../p__Application__index.c6e10f0b.async.js | 21 + .../fe/p__Audit__index.0aacc638.async.js | 18 + .../fe/p__Login__index.c0a83ac3.chunk.css | 18 + .../fe/p__Login__index.fbf461fe.async.js | 18 + ...p__SessionExpired__index.6d66b5a2.async.js | 18 + .../fe/p__Session__index.0fc8f8d6.async.js | 19 + .../fe/p__Workplace__index.1e0cb992.async.js | 18 + ...__plugin-layout__Layout.74b4118c.chunk.css | 18 + ...t__plugin-layout__Layout.7e7d859d.async.js | 18 + .../src/main/resources/fe/umi.9115c5be.js | 298 + .../src/main/resources/fe/umi.d85528d9.css | 18 + eiam-protocol/eiam-protocol-all/pom.xml | 66 + eiam-protocol/eiam-protocol-cas/pom.xml | 42 + .../cas/constant/ProtocolConstants.java | 36 + .../employee/protocol/cas/package-info.java | 18 + .../resources/templates/form_redirect.ftlh | 198 + .../resources/templates/jwt_redirect.ftlh | 198 + eiam-protocol/eiam-protocol-core/pom.xml | 35 + .../protocol/cas/constant/package-info.java | 18 + .../employee/protocol/cas/package-info.java | 18 + .../resources/templates/form_redirect.ftlh | 198 + .../resources/templates/jwt_redirect.ftlh | 198 + eiam-protocol/eiam-protocol-form/pom.xml | 43 + .../form/FormInitSingleSignOnEndpoint.java | 60 + .../form/constant/ProtocolConstants.java | 37 + .../employee/protocol/form/package-info.java | 18 + .../employee/protocol/package-info.java | 18 + .../resources/templates/form_redirect.ftlh | 198 + .../resources/templates/jwt_redirect.ftlh | 198 + eiam-protocol/eiam-protocol-jwt/pom.xml | 43 + .../jwt/constant/ProtocolConstants.java | 36 + .../employee/protocol/jwt/package-info.java | 18 + .../employee/protocol/package-info.java | 18 + .../resources/templates/form_redirect.ftlh | 198 + .../resources/templates/jwt_redirect.ftlh | 198 + eiam-protocol/eiam-protocol-oidc/pom.xml | 42 + .../EiamOAuth2AuthorizationService.java | 66 + ...mOAuth2InitSingleSignOnEndpointFilter.java | 311 + ...mOidcAuthorizationServerContextFilter.java | 132 + ...cationImplicitAuthenticationConverter.java | 158 + ...icationImplicitAuthenticationProvider.java | 375 + ...cationImplicitAuthenticationValidator.java | 214 + ...rizationImplicitAuthenticationContext.java | 106 + ...nImplicitAuthenticationEndpointFilter.java | 506 + ...zationImplicitAuthenticationException.java | 74 + ...horizationImplicitAuthenticationToken.java | 199 + ...nImplicitConsentAuthenticationContext.java | 176 + ...ImplicitConsentAuthenticationProvider.java | 278 + ...ionImplicitConsentAuthenticationToken.java | 136 + ...mplicitConsentAuthenticationConverter.java | 107 + ...zationPasswordAuthenticationConverter.java | 106 + ...izationPasswordAuthenticationProvider.java | 231 + ...horizationPasswordAuthenticationToken.java | 100 + .../oidc/constant/ProtocolConstants.java | 36 + .../protocol/oidc/context/package-info.java | 18 + .../endpoint/AbstractEiamEndpointFilter.java | 52 + ...dcProviderConfigurationEndpointFilter.java | 134 + .../protocol/oidc/endpoint/package-info.java | 18 + .../PortalOAuth2AuthenticationEntryPoint.java | 95 + .../protocol/oidc/handler/package-info.java | 24 + .../oidc/jwk/ApplicationJwkSource.java | 86 + .../employee/protocol/oidc/jwk/Jwks.java | 78 + .../protocol/oidc/jwk/KeyGeneratorUtils.java | 92 + .../oidc/jwt/ApplicationJwtDecoder.java | 77 + .../employee/protocol/oidc/package-info.java | 18 + .../OidcConfigRegisteredClientRepository.java | 155 + ...edisOAuth2AuthorizationConsentService.java | 75 + .../RedisOAuth2AuthorizationService.java | 167 + .../ApplicationOpaqueTokenIntrospector.java | 138 + .../oidc/token/EiamOAuth2TokenGenerator.java | 188 + .../protocol/oidc/util/EiamOAuth2Utils.java | 349 + ...OAuth2AuthorizationEndpointConfigurer.java | 290 + ...thorizationImplicitEndpointConfigurer.java | 296 + ...amOAuth2AuthorizationServerConfigurer.java | 345 + ...mOAuth2ClientAuthenticationConfigurer.java | 251 + .../EiamOAuth2TokenEndpointConfigurer.java | 249 + ...2TokenIntrospectionEndpointConfigurer.java | 220 + ...uth2TokenRevocationEndpointConfigurer.java | 218 + ...cClientRegistrationEndpointConfigurer.java | 239 + .../web/configurers/EiamOidcConfigurer.java | 136 + ...oviderConfigurationEndpointConfigurer.java | 79 + .../EiamOidcUserInfoEndpointConfigurer.java | 109 + eiam-protocol/eiam-protocol-saml2/pom.xml | 43 + .../saml2/constant/ProtocolConstants.java | 36 + .../saml2/idp/Saml2IdpConfigurer.java | 81 + .../configuration/OpenSamlConfiguration.java | 36 + .../Saml2IdpMetadataEndpointFilter.java | 132 + .../Saml2IdpSingleSignOnEndpointFilter.java | 296 + .../Saml2IdpSingleSignOutEndpointFilter.java | 133 + .../Saml2InitSingleSignOnEndpointFilter.java | 149 + .../idp/endpoint/xml/AssertionGenerator.java | 205 + .../xml/AttributeStatementGenerator.java | 74 + .../endpoint/xml/AuthnStatementGenerator.java | 61 + .../idp/endpoint/xml/ConditionsGenerator.java | 67 + .../xml/EncryptedAssertionGenerator.java | 77 + .../idp/endpoint/xml/IssuerGenerator.java | 48 + .../idp/endpoint/xml/ResponseGenerator.java | 210 + .../idp/endpoint/xml/Saml2ValidatorSuite.java | 128 + .../idp/endpoint/xml/StatusGenerator.java | 54 + .../xml/SubjectConfirmationGenerator.java | 72 + .../idp/endpoint/xml/SubjectGenerator.java | 39 + ...Saml2AuthorizationServerContextFilter.java | 93 + .../protocol/saml2/idp/package-info.java | 18 + .../protocol/saml2/idp/util/Saml2Utils.java | 159 + .../protocol/saml2/idp/util/package-info.java | 18 + .../employee/protocol/saml2/package-info.java | 18 + .../resources/templates/form_redirect.ftlh | 198 + .../resources/templates/jwt_redirect.ftlh | 198 + eiam-protocol/pom.xml | 52 + eiam-support/pom.xml | 32 + .../employee/support/UtilsAndCommons.java | 32 + .../cn/topiam/employee/support/Version.java | 51 + .../ExceptionHandlingAsyncTaskExecutor.java | 129 + .../support/cache/TopIamKeyGenerator.java | 55 + .../employee/support/cache/package-info.java | 24 + .../ConfigRefreshListenerRunner.java | 95 + .../support/constant/EiamConstants.java | 169 + .../support/constant/package-info.java | 24 + .../context/ApplicationContextHelp.java | 109 + .../ApplicationContextHelpInitializer.java | 37 + .../support/context/PublicBaseUrlBuilder.java | 61 + .../support/context/ServletContextHelp.java | 361 + .../support/context/package-info.java | 22 + .../support/error/TopIamErrorAttributes.java | 104 + .../support/error/TopIamErrorStaticView.java | 127 + .../support/excel/DataImportFailReason.java | 56 + .../support/excel/DataImportResult.java | 52 + .../employee/support/excel/ExcelUtils.java | 137 + .../excel/ImportDataValidationException.java | 38 + .../employee/support/excel/package-info.java | 24 + .../exception/AuthenticationException.java | 31 + .../support/exception/BadParamsException.java | 59 + .../exception/HaveNotAuthorityException.java | 45 + .../exception/InfoValidityFailException.java | 50 + .../InteractionRequiredException.java | 29 + .../exception/NotActivatedException.java | 60 + .../support/exception/NotExistException.java | 60 + .../support/exception/TopIamException.java | 198 + .../UnknownAuthenticationTypeException.java | 50 + .../exception/UserNotBoundException.java | 55 + .../exception/enums/ExceptionStatus.java | 147 + .../support/exception/package-info.java | 22 + .../cn/topiam/employee/support/lock/Lock.java | 53 + .../employee/support/lock/LockAspect.java | 103 + .../support/lock/TopIamLockException.java | 99 + .../employee/support/lock/package-info.java | 24 + .../employee/support/node/BaseNode.java | 52 + .../employee/support/node/NodeUtils.java | 61 + .../topiam/employee/support/package-info.java | 24 + .../support/preview/DemoEnvAspect.java | 77 + ...DemoEnvDoesNotAllowOperationException.java | 34 + .../employee/support/preview/Preview.java | 31 + .../support/repository/domain/BaseEntity.java | 93 + .../repository/domain/BaseTenantEntity.java | 61 + .../repository/domain/BatchBaseEntity.java | 98 + .../support/repository/domain/IdEntity.java | 55 + .../repository/domain/package-info.java | 23 + .../repository/id/SnowflakeIdGenerator.java | 82 + .../support/repository/package-info.java | 24 + .../support/repository/page/domain/Page.java | 65 + .../repository/page/domain/PageModel.java | 95 + .../page/domain/QueryDslRequest.java | 42 + .../util/BooleanExpressionUtils.java | 140 + .../repository/util/QuerydslUtils.java | 257 + .../request/ParameterRequestWrapper.java | 121 + .../support/result/ApiRestResult.java | 300 + .../employee/support/result/package-info.java | 23 + .../employee/support/snowflake/Snowflake.java | 170 + .../task/TaskSchedulerRegistrarHelp.java | 79 + .../trace/MdcThreadPoolTaskExecutor.java | 132 + .../topiam/employee/support/trace/Trace.java | 34 + .../employee/support/trace/TraceAspect.java | 62 + .../support/trace/TraceAutoConfigurer.java | 48 + .../employee/support/trace/TraceFilter.java | 86 + .../employee/support/trace/TraceUtils.java | 50 + .../employee/support/trace/package-info.java | 23 + .../employee/support/util/AesUtils.java | 109 + .../support/util/AppVersionUtils.java | 47 + .../employee/support/util/BeanUtils.java | 180 + .../employee/support/util/CertUtils.java | 241 + .../employee/support/util/CountryUtils.java | 293 + .../employee/support/util/CreateFileUtil.java | 125 + .../employee/support/util/DateUtils.java | 79 + .../support/util/DesensitizationUtil.java | 143 + .../support/util/HttpClientUtils.java | 487 + .../support/util/HttpResponseUtils.java | 117 + .../employee/support/util/HttpUrlUtils.java | 47 + .../topiam/employee/support/util/IpUtils.java | 144 + .../employee/support/util/JsonUtils.java | 212 + .../employee/support/util/Md5Utils.java | 157 + .../employee/support/util/PhoneUtils.java | 68 + .../employee/support/util/Pinyin4jUtils.java | 188 + .../employee/support/util/QrCodeUtils.java | 60 + .../support/util/ReflectionUtils.java | 105 + .../support/util/RestTemplateUtils.java | 239 + .../employee/support/util/RsaUtils.java | 270 + .../employee/support/util/SpelUtils.java | 61 + .../employee/support/util/StringUtils.java | 104 + .../employee/support/util/UrlTestUtils.java | 48 + .../employee/support/util/package-info.java | 18 + .../support/validation/ValidationHelp.java | 185 + .../support/validation/annotation/Phone.java | 42 + .../annotation/ValidationPhone.java | 55 + .../validation/annotation/package-info.java | 18 + .../support/validation/package-info.java | 23 + .../support/web/converter/EnumConvert.java | 33 + .../web/converter/EnumConverterFactory.java | 106 + .../support/web/converter/package-info.java | 24 + .../employee/support/web/package-info.java | 24 + .../support/web/useragent/UserAgent.java | 56 + .../support/web/useragent/UserAgentUtils.java | 128 + .../support/web/useragent/package-info.java | 18 + eiam-synchronizer/Dockerfile | 48 + eiam-synchronizer/pom.xml | 67 + .../employee/EiamSynchronizerApplication.java | 38 + .../topiam/employee/ServletInitializer.java | 36 + .../IdentitySourceBeanRegistry.java | 331 + .../IdentitySourceBeanUtils.java | 39 + .../SynchronizerSecurityConfiguration.java | 63 + .../configuration/package-info.java | 18 + .../constants/SynchronizerConstants.java | 39 + .../IdentitySourceEventReceiveEndpoint.java | 85 + .../synchronizer/endpoint/package-info.java | 18 + .../synchronizer/mapper/package-info.java | 18 + .../employee/synchronizer/package-info.java | 18 + .../AbstractIdentitySourcePostProcessor.java | 344 + ...efaultIdentitySourceDeptPostProcessor.java | 640 + ...faultIdentitySourceEventPostProcessor.java | 555 + ...efaultIdentitySourceUserPostProcessor.java | 744 ++ .../task/IdentitySourceSyncTask.java | 105 + .../synchronizer/task/package-info.java | 18 + .../src/main/resources/application.yml | 162 + lombok.config | 2 + mvnw | 316 + mvnw.cmd | 188 + pom.xml | 747 ++ tools/codestyle/Formatter.xml | 400 + 1479 files changed, 143796 insertions(+) create mode 100644 .gitignore create mode 100644 .mvn/jvm.config create mode 100644 .mvn/maven.config create mode 100644 .mvn/wrapper/maven-wrapper.jar create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 eiam-alert/pom.xml create mode 100644 eiam-application/eiam-application-all/pom.xml create mode 100644 eiam-application/eiam-application-cas/pom.xml create mode 100644 eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/AbstractCasApplicationService.java create mode 100644 eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/CasStandardApplicationServiceImpl.java create mode 100644 eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/package-info.java create mode 100644 eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/package-info.java create mode 100644 eiam-application/eiam-application-core/pom.xml create mode 100644 eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/AbstractApplicationService.java create mode 100644 eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/ApplicationService.java create mode 100644 eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/ApplicationServiceLoader.java create mode 100644 eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/Saml2ApplicationService.java create mode 100644 eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/SamlRamRoleNameValueType.java create mode 100644 eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/context/ApplicationContext.java create mode 100644 eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/context/ApplicationContextHolder.java create mode 100644 eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/context/package-info.java create mode 100644 eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppCertNotExistException.java create mode 100644 eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppConfigNotExistException.java create mode 100644 eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppNotEnableException.java create mode 100644 eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppNotExistException.java create mode 100644 eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppTemplateNotExistException.java create mode 100644 eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/ParseSaml2MetadataException.java create mode 100644 eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/package-info.java create mode 100644 eiam-application/eiam-application-form/pom.xml create mode 100644 eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/AbstractFormApplicationService.java create mode 100644 eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/FormStandardApplicationServiceImpl.java create mode 100644 eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/model/AppFormConfigGetResult.java create mode 100644 eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/model/package-info.java create mode 100644 eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/package-info.java create mode 100644 eiam-application/eiam-application-jwt/pom.xml create mode 100644 eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/AbstractJwtApplicationService.java create mode 100644 eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/JwtStandardApplicationServiceImpl.java create mode 100644 eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/model/AppJwtGetResult.java create mode 100644 eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/model/package-info.java create mode 100644 eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/package-info.java create mode 100644 eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/package-info.java create mode 100644 eiam-application/eiam-application-oidc/pom.xml create mode 100644 eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/AbstractOidcApplicationService.java create mode 100644 eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/OidcStandardApplicationServiceImpl.java create mode 100644 eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/converter/AppOidcStandardConfigConverter.java create mode 100644 eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/model/AppOidcStandardConfigGetResult.java create mode 100644 eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/model/AppOidcStandardSaveConfigParam.java create mode 100644 eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/model/package-info.java create mode 100644 eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/package-info.java create mode 100644 eiam-application/eiam-application-saml2/pom.xml create mode 100644 eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/package-info.java create mode 100644 eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/AbstractSamlAppService.java create mode 100644 eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/Saml2StandardApplicationServiceImpl.java create mode 100644 eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/converter/AppSaml2StandardConfigConverter.java create mode 100644 eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/AppSaml2StandardConfigGetResult.java create mode 100644 eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/AppSaml2StandardSaveConfigParam.java create mode 100644 eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/Saml2ConverterUtils.java create mode 100644 eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/Saml2ProtocolEndpoint.java create mode 100644 eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/package-info.java create mode 100644 eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/package-info.java create mode 100644 eiam-application/pom.xml create mode 100644 eiam-audit/pom.xml create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/Audit.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/AuditAspect.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/AuditExpressionOperations.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/AuditExpressionRoot.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/package-info.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/configuration/ElasticsearchConfiguration.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/context/AuditContext.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/controller/AuditController.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/controller/pojo/AuditDictResult.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/controller/pojo/AuditListQuery.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/controller/pojo/AuditListResult.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/entity/Actor.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/entity/AuditElasticSearchEntity.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/entity/AuditEntity.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/entity/Event.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/entity/GeoLocation.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/entity/Target.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/entity/UserAgent.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/enums/EventStatus.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/enums/EventType.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/enums/TargetType.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/enums/converter/AuditTypeConverter.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/enums/converter/EventStatusConverter.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/enums/converter/TargetTypeConverter.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/event/AuditEvent.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/event/AuditEventListener.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/event/AuditEventPublish.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/AccountEventType.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/AppEventType.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/AuthenticationEventType.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/OtherEventType.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/Resource.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/SettingEventType.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/Type.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/package-info.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/repository/AuditRepository.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/repository/package-info.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/service/AuditService.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/service/converter/AuditDataConverter.java create mode 100644 eiam-audit/src/main/java/cn/topiam/employee/audit/service/impl/AuditServiceImpl.java create mode 100644 eiam-authentication/eiam-authentication-all/pom.xml create mode 100644 eiam-authentication/eiam-authentication-core/pom.xml create mode 100644 eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/AbstractIdentityProviderService.java create mode 100644 eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/IdentityProviderService.java create mode 100644 eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/IdentityProviderServiceLoader.java create mode 100644 eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/config/IdentityProviderConfig.java create mode 100644 eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/exception/IdentityProviderTemplateNotExistException.java create mode 100644 eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/filter/AbstractIdpAuthenticationProcessingFilter.java create mode 100644 eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/modal/IdpUser.java create mode 100644 eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/package-info.java create mode 100644 eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/service/UserIdpService.java create mode 100644 eiam-authentication/eiam-authentication-dingtalk/pom.xml create mode 100644 eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/DingTalkIdpOauthConfig.java create mode 100644 eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/DingTalkIdpScanCodeConfig.java create mode 100644 eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/configurer/DingtalkOAuth2AuthenticationConfigurer.java create mode 100644 eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/configurer/DingtalkScanCodeAuthenticationConfigurer.java create mode 100644 eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/constant/DingTalkAuthenticationConstants.java create mode 100644 eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/filter/DingtalkOAuth2AuthorizationRequestRedirectFilter.java create mode 100644 eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/filter/DingtalkOauthAuthenticationFilter.java create mode 100644 eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/filter/DingtalkScanCodeAuthenticationFilter.java create mode 100644 eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/filter/DingtalkScanCodeAuthorizationRequestGetFilter.java create mode 100644 eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/package-info.java create mode 100644 eiam-authentication/eiam-authentication-feishu/pom.xml create mode 100644 eiam-authentication/eiam-authentication-feishu/src/main/java/cn/topiam/employee/authentication/feishu/configurer/package-info.java create mode 100644 eiam-authentication/eiam-authentication-feishu/src/main/java/cn/topiam/employee/authentication/feishu/filter/FeiShuAuthenticationFilter.java create mode 100644 eiam-authentication/eiam-authentication-feishu/src/main/java/cn/topiam/employee/authentication/feishu/package-info.java create mode 100644 eiam-authentication/eiam-authentication-mfa/pom.xml create mode 100644 eiam-authentication/eiam-authentication-mfa/src/main/java/cn/topiam/employee/authentication/mfa/package-info.java create mode 100644 eiam-authentication/eiam-authentication-qq/pom.xml create mode 100644 eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/QqIdpOauthConfig.java create mode 100644 eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/configurer/QqOauthAuthenticationConfigurer.java create mode 100644 eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/constant/QqAuthenticationConstants.java create mode 100644 eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/filter/QqOAuth2AuthorizationRequestRedirectFilter.java create mode 100644 eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/filter/QqOAuth2LoginAuthenticationFilter.java create mode 100644 eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/package-info.java create mode 100644 eiam-authentication/eiam-authentication-sms/pom.xml create mode 100644 eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/configurer/SmsAuthenticationConfigurer.java create mode 100644 eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/configurer/package-info.java create mode 100644 eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/filter/SmsAuthenticationFilter.java create mode 100644 eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/filter/package-info.java create mode 100644 eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/package-info.java create mode 100644 eiam-authentication/eiam-authentication-wechat/pom.xml create mode 100644 eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/WeChatIdpScanCodeConfig.java create mode 100644 eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/configurer/WeChatScanCodeAuthenticationConfigurer.java create mode 100644 eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/constant/WeChatAuthenticationConstants.java create mode 100644 eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/filter/WeChatScanCodeAuthorizationRequestRedirectFilter.java create mode 100644 eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/filter/WeChatScanCodeLoginAuthenticationFilter.java create mode 100644 eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/package-info.java create mode 100644 eiam-authentication/eiam-authentication-wechatwork/pom.xml create mode 100644 eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/WeChatWorkIdpScanCodeConfig.java create mode 100644 eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/configurer/WeChatWorkScanCodeAuthenticationConfigurer.java create mode 100644 eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/constant/WeChatWorkAuthenticationConstants.java create mode 100644 eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/filter/WeChatWorkScanCodeAuthorizationRequestRedirectFilter.java create mode 100644 eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/filter/WeChatWorkScanCodeLoginAuthenticationFilter.java create mode 100644 eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/package-info.java create mode 100644 eiam-authentication/pom.xml create mode 100644 eiam-common/pom.xml create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/constants/AccountConstants.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/constants/AnalysisConstants.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/constants/AppConstants.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/constants/AuditConstants.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/constants/AuthenticationConstants.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/constants/AuthorizeConstants.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/constants/CaptchaConstants.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/constants/CommonConstants.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/constants/ConfigBeanNameConstants.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/constants/ProtocolConstants.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/constants/SessionConstants.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/constants/SettingConstants.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/constants/StorageConstants.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/MailSendRecordEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/SmsSendRecordEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/account/OrganizationEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/account/OrganizationMemberEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserDetailEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserGroupEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserGroupMemberEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserHistoryPasswordEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserIdpBindEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/account/po/UserIdpBindPo.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/account/po/UserPO.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/account/query/UserGroupMemberListQuery.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/account/query/UserListNotInGroupQuery.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/account/query/UserListQuery.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/analysis/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppAccessPolicyEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppAccountEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppCertEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppOidcConfigEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppPermissionActionEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppPermissionPolicyEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppPermissionResourceEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppPermissionRoleEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppSaml2ConfigEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppAccessPolicyPO.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppAccountPO.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppOidcConfigPO.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppPermissionPolicyPO.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppSaml2ConfigPO.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/app/query/AppAccessPolicyQuery.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/app/query/AppAccountQuery.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/app/query/AppPolicyQuery.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/app/standard/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/authentication/IdentityProviderEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/IdentitySourceEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/IdentitySourceEventRecordEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/IdentitySourceSyncHistoryEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/IdentitySourceSyncRecordEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/config/JobConfig.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/config/StrategyConfig.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/setting/AdministratorEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/setting/MailTemplateEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/setting/SettingEntity.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/entity/setting/config/SmsConfig.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/AuthenticationType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/BaseEnum.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/CaptchaProviderType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/CheckValidityType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/DataOrigin.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/EnumDeserializer.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/IdentityProviderCategory.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/IdentityProviderType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/Language.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/ListEnumDeserializer.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/MailType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/MessageCategory.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/MessageNoticeChannel.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/MfaFactor.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/MfaMode.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/OrganizationType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/PasswordStrength.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/PermissionActionType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/PolicyEffect.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/PolicyObjectType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/PolicySubjectType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/SecretType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/SmsType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/SyncStatus.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/TriggerType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/UserGender.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/UserIdType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/UserStatus.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/UserType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AppCertUsingType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AppProtocol.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AppType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AttributeNameFormat.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AuthnContextClassRefType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AuthorizationType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/InitLoginType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlAttributeStatementValueType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlEncryptAssertAlgorithmType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlNameIdFormatType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlNameIdValueType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlParseMetadataType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlSignAssertAlgorithmType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlSignResponseAlgorithmType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/AppCertUsingTypeConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/AppProtocolConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/AppTypeConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlAttributeNameFormatConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlAttributeStatementValueTypeConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlAuthnContextClassRefTypeConvert.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlEncryptAssertAlgorithmTypeConvert.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlNameIdFormatTypeConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlNameIdTypeConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlSignAssertAlgorithmTypeConvert.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlSignResponseAlgorithmTypeConvert.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SsoInitiatorConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SsoScopeConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/DataOriginConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/IdentityProviderCategoryConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/IdentityProviderTypeConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/LanguageConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/MailTypeConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/MessageCategoryConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/MfaMannerConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/MfaModeConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/OrganizationTypeConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/PermissionActionConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/PolicyEffectConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/PolicyObjectTypeConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/PolicySubjectConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/SmsTypeConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/SyncStatusConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/TriggerTypeConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/UserIdTypeConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/UserStatusConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/UserTypeConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/identityprovider/IdentitySourceProvider.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/identityprovider/converter/IdentitySourceProviderConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/identitysource/IdentitySourceActionType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/identitysource/IdentitySourceObjectType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/identitysource/converter/IdentitySourceActionTypeConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/identitysource/converter/IdentitySourceObjectTypeConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/setting/converter/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/enums/setting/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/exception/BindMfaNotFoundSecretException.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/exception/InvalidMfaCodeException.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/exception/LoginOtpActionNotSupportException.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/exception/MailMessageSendException.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/exception/MailProviderException.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/exception/MailTemplateException.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/exception/MessageSendException.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/exception/OtpSendException.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/exception/PasswordValidatedFailException.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/exception/PrepareBindMfaException.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/exception/UserNotFoundException.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppAccountExistException.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppAccountNotExistException.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppPolicyNotExistException.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppResourceNotExistException.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppRoleNotExistException.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/exception/app/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/exception/handler/GlobalExceptionHandler.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/geo/GeoLocation.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/geo/GeoLocationProviderConfig.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/geo/GeoLocationService.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/geo/NoneGeoLocationServiceImpl.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/MaxmindGeoLocationServiceImpl.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/MaxmindProviderConfig.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/UpdateMaxmindTaskConfiguration.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/enums/GeoLocationProvider.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/geo/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/geo/taobao/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/SendMessageException.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/SmsProviderException.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/enums/MailProvider.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/enums/MailSafetyType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/enums/MessageType.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/enums/SmsProvider.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/enums/convert/MailProviderPlatformConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/enums/convert/MailSafetyTypeConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/enums/convert/MessageTypeConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/enums/convert/SmsProviderConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/mail/DefaultMailProviderSendImpl.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/mail/MailNoneProviderSend.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/mail/MailProviderConfig.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/mail/MailProviderSend.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/mail/SendMailRequest.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/mail/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SendSmsRequest.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsNoneProviderSend.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsProviderConfig.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsProviderSend.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsResponse.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsSendProviderFactory.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/sms/aliyun/AliyunSmsProviderConfig.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/sms/aliyun/AliyunSmsProviderSend.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/sms/aliyun/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/sms/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/sms/qiniu/QiNiuSmsProviderConfig.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/sms/qiniu/QiNiuSmsProviderSend.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/sms/qiniu/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/sms/tencent/TencentSmsProviderConfig.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/sms/tencent/TencentSmsProviderSend.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/message/sms/tencent/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/MailSendRecordRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/SmsSendRecordRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/OrganizationMemberCustomizedRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/OrganizationMemberRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/OrganizationRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/OrganizationRepositoryCustomized.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserDetailRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserDetailRepositoryCustomized.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserGroupMemberRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserGroupMemberRepositoryCustomized.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserGroupRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserHistoryPasswordRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserIdpRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserIdpRepositoryCustomized.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserRepositoryCustomized.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/OrganizationMemberCustomizedRepositoryImpl.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/OrganizationRepositoryCustomizedImpl.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/UserDetailRepositoryCustomizedImpl.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/UserGroupMemberRepositoryCustomizedImpl.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/UserIdpRepositoryCustomizedImpl.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/UserRepositoryCustomizedImpl.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/mapper/UserEntityMapper.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/mapper/UserIdpBindPoMapper.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/mapper/UserPoMapper.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppAccessPolicyRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppAccessPolicyRepositoryCustomized.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppAccountRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppAccountRepositoryCustomized.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppCertRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppOidcConfigRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppOidcConfigRepositoryCustomized.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionActionRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionPolicyRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionPolicyRepositoryCustomized.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionResourceRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionRoleRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppRepositoryCustomized.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppSaml2ConfigRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppSaml2ConfigRepositoryCustomized.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppAccessPolicyRepositoryCustomizedImpl.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppAccountRepositoryCustomizedImpl.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppOidcConfigRepositoryCustomizedImpl.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppPermissionPolicyRepositoryCustomizedImpl.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppRepositoryCustomizedImpl.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppSaml2ConfigRepositoryCustomizedImpl.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/ResourceRepositoryCustomizedImpl.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/RoleRepositoryCustomizedImpl.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppAccessPolicyPoMapper.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppAccountPoMapper.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppEntityMapper.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppOidcConfigPoMapper.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppPermissionPolicyPoMapper.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppSaml2ConfigPoMapper.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/app/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/authentication/IdentityProviderRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/authorization/ResourceRepositoryCustomized.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/authorization/RoleRepositoryCustomized.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceEventRecordRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceEventRecordRepositoryCustomized.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceSyncHistoryRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceSyncRecordRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceSyncRecordRepositoryCustomized.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/impl/IdentitySourceEventRecordRepositoryCustomizedImpl.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/impl/IdentitySourceSyncRecordRepositoryCustomizedImpl.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/setting/AdministratorRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/setting/MailTemplateRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/repository/setting/SettingRepository.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/storage/AbstractStorage.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/storage/Storage.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/storage/StorageConfig.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/storage/StorageFactory.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/storage/StorageProviderException.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/storage/controller/StorageFileResource.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/storage/enums/StorageProvider.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/storage/enums/convert/StorageProviderConverter.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/AliYunOssStorage.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/LocalStorage.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/MinIoStorage.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/QiNiuKodoStorage.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/TencentCosStorage.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/storage/package-info.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/util/InputStreamMetadataResolver.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/util/RequestUtils.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/util/SamlKeyStoreProvider.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/util/SamlUtils.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/util/X509Utilities.java create mode 100644 eiam-common/src/main/java/cn/topiam/employee/common/util/package-info.java create mode 100644 eiam-common/src/main/resources/config/logback-spring.xml create mode 100644 eiam-common/src/main/resources/db/changelog/administrator-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/app-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/app_access_policy-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/app_account-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/app_cert-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/app_oidc_config-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/app_permission_action-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/app_permission_policy-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/app_permission_resource-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/app_permission_role-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/app_saml2_config-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/audit-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/identity_provider-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/identity_source-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/identity_source_event_record-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/identity_source_sync_history-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/identity_source_sync_record-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/mail_send_record-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/mail_template-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/organization-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/organization_member-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/setting-changlog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/sms_send_record-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/user-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/user_detail-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/user_group-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/user_group_member-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/user_history_password-changelog.xml create mode 100644 eiam-common/src/main/resources/db/changelog/user_idp_bind-changlog.xml create mode 100644 eiam-common/src/main/resources/db/eiam-changelog-master.xml create mode 100644 eiam-common/src/main/resources/dictionaries/10k-most-common.txt create mode 100644 eiam-common/src/main/resources/mail/content/again-verify-mail-content.html create mode 100644 eiam-common/src/main/resources/mail/content/bind-mail-content.html create mode 100644 eiam-common/src/main/resources/mail/content/password-soon-expired-content.html create mode 100644 eiam-common/src/main/resources/mail/content/reset-password-confirm-content.html create mode 100644 eiam-common/src/main/resources/mail/content/reset-password-content.html create mode 100644 eiam-common/src/main/resources/mail/content/update-bind-mail-content.html create mode 100644 eiam-common/src/main/resources/mail/content/update-password-content.html create mode 100644 eiam-common/src/main/resources/mail/content/verify-email-content.html create mode 100644 eiam-common/src/main/resources/mail/content/warning-content.html create mode 100644 eiam-common/src/main/resources/mail/content/welcome-mail-content.html create mode 100644 eiam-common/src/main/resources/sms/template/sms-template_en.properties create mode 100644 eiam-common/src/main/resources/sms/template/sms-template_zh.properties create mode 100644 eiam-console/Dockerfile create mode 100644 eiam-console/pom.xml create mode 100644 eiam-console/src/main/java/cn/topiam/employee/EiamConsoleApplication.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/ServletInitializer.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/configuration/ConsoleApiConfiguration.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/configuration/ConsoleFrontendConfiguration.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/configuration/ConsoleSecurityConfiguration.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/configuration/package-info.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/constants/package-info.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/CurrentUserEndpoint.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/FrontendForwardController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/account/OrganizationController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/account/UserController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/account/UserGroupController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/account/UserIdpBindController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/analysis/AnalysisController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/analysis/package-info.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppAccessPolicyController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppAccountController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppCertController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppPermissionActionController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppPermissionPolicyController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppPermissionResourceController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppPermissionRoleController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppSaml2Controller.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppTemplateController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/authentication/IdentityProviderController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/identitysource/IdentitySourceController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/identitysource/IdentitySourceEventController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/identitysource/IdentitySourceSyncController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/package-info.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/session/SessionManageEndpoint.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/AdministratorController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/GeoIpLibraryController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/MailProviderController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/MailTemplateController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/SecurityController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/SmsProviderController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/SmsTemplateController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/StorageController.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/account/OrganizationConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/account/UserConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/account/UserGroupConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppAccessPolicyConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppAccountConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppCertConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppPermissionActionConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppPermissionPolicyConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppPermissionResourceConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppPermissionRoleConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/app/UserIdpBindConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/authentication/IdentityProviderConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/identitysource/IdentitySourceConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/identitysource/IdentitySourceEventRecordConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/identitysource/IdentitySourceSyncConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/package-info.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/AdministratorConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/GeoLocationSettingConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/MailTemplateConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/MessageSettingConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/PasswordPolicyConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/SecuritySettingConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/StorageSettingConverter.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/listener/ConsoleAdminPasswordInitializeListener.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/listener/package-info.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/package-info.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/other/IdentitySourceConfigValidatorParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/other/OrganizationExcelData.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/other/package-info.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/package-info.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/account/UserGroupListQuery.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/analysis/AnalysisQuery.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppCertQuery.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppPermissionActionListQuery.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppPermissionRoleListQuery.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppQuery.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppResourceListQuery.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/authentication/IdentityProviderListQuery.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/authentication/package-info.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/identity/IdentitySourceEventRecordListQuery.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/identity/IdentitySourceListQuery.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/identity/IdentitySourceSyncHistoryListQuery.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/identity/IdentitySourceSyncRecordListQuery.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/package-info.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/setting/AdministratorListQuery.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/OrganizationChildResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/OrganizationResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/OrganizationRootResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/OrganizationTreeResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserGroupListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserGroupMemberListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserGroupResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserLoginAuditListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/analysis/AppVisitRankResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/analysis/AuthnHotProviderResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/analysis/AuthnQuantityResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/analysis/OverviewResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppAccessPolicyResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppAccountListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppCertListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppCreateResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppGetResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionActionListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionPolicyGetResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionPolicyListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionResourceGetResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionResourceListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionRoleListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionRoleResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppTemplateResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/ParseSaml2MetadataResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/UserIdpBindListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/authentication/IdentityProviderCreateResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/authentication/IdentityProviderListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/authentication/IdentityProviderResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceConfigGetResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceEventRecordListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceGetResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceSyncHistoryListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceSyncRecordListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/package-info.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/AdministratorListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/AdministratorResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/EmailProviderConfigResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/EmailTemplateListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/EmailTemplateResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/GeoIpProviderResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/PasswordPolicyConfigResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/SecurityBasicConfigResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/SecurityCaptchaConfigResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/SecurityMfaConfigResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/SmsTemplateListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/StorageProviderConfigResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/WeakPasswordLibListResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/account/OrganizationCreateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/account/UserCreateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/account/UserGroupCreateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppAccessPolicyCreateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppAccountCreateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppCreateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppPermissionPolicyCreateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppPermissionResourceCreateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppPermissionRoleCreateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppPermissionsActionParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppSaveConfigParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/authentication/IdentityProviderCreateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/authentication/InitializeAdminSaveParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/identitysource/IdentitySourceConfigSaveParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/identitysource/IdentitySourceCreateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/identitysource/IdentitySourceCreateResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/AdministratorCreateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/EmailCustomTemplateSaveParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/GeoIpProviderSaveParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/MailProviderSaveParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/PasswordPolicySaveParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/SecurityBasicSaveParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/SecurityCaptchaSaveParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/SecurityMfaSaveParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/SmsProviderSaveParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/StorageConfigSaveParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/setting/SmsProviderConfigResult.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/account/OrganizationUpdateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/account/ResetPasswordParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/account/UserGroupUpdateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/account/UserUpdateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/AppPermissionPolicyUpdateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/AppPermissionResourceUpdateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/AppSaveConfigParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/AppUpdateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/PermissionRoleUpdateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/authentication/IdpUpdateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/identity/IdentitySourceUpdateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/setting/AdministratorUpdateParam.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleAccessDeniedHandler.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleAuthenticationEntryPoint.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleAuthenticationFailureHandler.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleAuthenticationSuccessHandler.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleLogoutSuccessHandler.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/security/handler/package-info.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/security/listener/ConsoleAuthenticationFailureEventListener.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/security/listener/ConsoleAuthenticationSuccessEventListener.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/security/listener/ConsoleLogoutSuccessEventListener.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/security/listener/ConsoleSessionInformationExpiredStrategy.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/security/package-info.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/account/OrganizationService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/account/UserAccountAssociateService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/account/UserGroupService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/account/UserService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/account/UserSocialBindService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/OrganizationServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/UserAccountAssociateServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/UserGroupServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/UserServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/UserSocialBindServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/account/userdetail/UserDetailsServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/analysis/AnalysisService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/analysis/impl/AnalysisServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/analysis/package-info.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppAccessPolicyService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppAccountService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppCertService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppPermissionActionService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppPermissionPolicyService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppPermissionResourceService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppPermissionRoleService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppSaml2Service.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppTemplateService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/UserIdpBindService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppAccessPolicyServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppAccountServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppCertServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppPermissionActionServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppPermissionPolicyServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppPermissionResourceServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppPermissionRoleServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppSaml2ServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppTemplateServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/UserIdpBindServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/app/package-info.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/authentication/IdentityProviderService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/authentication/impl/IdentityProviderServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/IdentitySourceEventRecordService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/IdentitySourceService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/IdentitySourceSyncService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/impl/IdentitySourceEventRecordServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/impl/IdentitySourceServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/impl/IdentitySourceSyncServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/package-info.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/setting/AdministratorService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/setting/GeoLocationSettingService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/setting/MailTemplateService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/setting/MessageSettingService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/setting/PasswordPolicyService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/setting/SecuritySettingService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/setting/SettingService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/setting/SmsTemplateService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/setting/StorageSettingService.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/AdministratorServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/GeoLocationSettingServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/MailTemplateServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/MessageSettingServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/PasswordPolicyServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/SecuritySettingServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/SettingServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/SmsTemplateServiceImpl.java create mode 100644 eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/StorageSettingServiceImpl.java create mode 100644 eiam-console/src/main/resources/application.yml create mode 100644 eiam-console/src/main/resources/fe/1098.d7361839.async.js create mode 100644 eiam-console/src/main/resources/fe/1158.74b40769.async.js create mode 100644 eiam-console/src/main/resources/fe/137.d372bd41.async.js create mode 100644 eiam-console/src/main/resources/fe/1576.0ddabdb9.async.js create mode 100644 eiam-console/src/main/resources/fe/1687.d91fbb33.async.js create mode 100644 eiam-console/src/main/resources/fe/1914.479e05f1.async.js create mode 100644 eiam-console/src/main/resources/fe/211.eb7863f3.async.js create mode 100644 eiam-console/src/main/resources/fe/2172.0a9d7ed7.async.js create mode 100644 eiam-console/src/main/resources/fe/2181.f36b14fa.async.js create mode 100644 eiam-console/src/main/resources/fe/2387.e263de85.async.js create mode 100644 eiam-console/src/main/resources/fe/2406.b269972c.async.js create mode 100644 eiam-console/src/main/resources/fe/2453.55350af8.async.js create mode 100644 eiam-console/src/main/resources/fe/2657.26d96d2a.async.js create mode 100644 eiam-console/src/main/resources/fe/3956.63df22b1.async.js create mode 100644 eiam-console/src/main/resources/fe/4393.9e9cdbf7.async.js create mode 100644 eiam-console/src/main/resources/fe/4674.f866949b.async.js create mode 100644 eiam-console/src/main/resources/fe/4968.8584392a.async.js create mode 100644 eiam-console/src/main/resources/fe/5175.b7e3d2b9.async.js create mode 100644 eiam-console/src/main/resources/fe/5642.0270a7ef.async.js create mode 100644 eiam-console/src/main/resources/fe/6162.d72b1f25.async.js create mode 100644 eiam-console/src/main/resources/fe/6495.cbafa057.async.js create mode 100644 eiam-console/src/main/resources/fe/7468.1b93dfe0.async.js create mode 100644 eiam-console/src/main/resources/fe/7565.e8ec7e12.async.js create mode 100644 eiam-console/src/main/resources/fe/7640.94326ec9.async.js create mode 100644 eiam-console/src/main/resources/fe/7785.bdf19941.async.js create mode 100644 eiam-console/src/main/resources/fe/7980.263c2c95.async.js create mode 100644 eiam-console/src/main/resources/fe/8138.96849e31.async.js create mode 100644 eiam-console/src/main/resources/fe/8345.b475777a.async.js create mode 100644 eiam-console/src/main/resources/fe/918.adb4d8ee.async.js create mode 100644 eiam-console/src/main/resources/fe/9299.8ec110c6.async.js create mode 100644 eiam-console/src/main/resources/fe/9400.7f60eb97.async.js create mode 100644 eiam-console/src/main/resources/fe/9825.35d12015.async.js create mode 100644 eiam-console/src/main/resources/fe/ade5b70f.jpg create mode 100644 eiam-console/src/main/resources/fe/favicon.ico create mode 100644 eiam-console/src/main/resources/fe/full-logo-white.svg create mode 100644 eiam-console/src/main/resources/fe/full-logo.svg create mode 100644 eiam-console/src/main/resources/fe/index.html create mode 100644 eiam-console/src/main/resources/fe/login-background.png create mode 100644 eiam-console/src/main/resources/fe/logo.svg create mode 100644 eiam-console/src/main/resources/fe/p__404.11bb088a.async.js create mode 100644 eiam-console/src/main/resources/fe/p__Welcome.3630b9f4.async.js create mode 100644 eiam-console/src/main/resources/fe/p__account__IdentitySourceDetail__index.28baad45.async.js create mode 100644 eiam-console/src/main/resources/fe/p__account__IdentitySourceList__index.4fdd0185.async.js create mode 100644 eiam-console/src/main/resources/fe/p__account__List__index.04f77279.async.js create mode 100644 eiam-console/src/main/resources/fe/p__account__UserDetail__index.6d202951.async.js create mode 100644 eiam-console/src/main/resources/fe/p__account__UserGroupDetail__index.28afa610.async.js create mode 100644 eiam-console/src/main/resources/fe/p__account__UserGroupList__index.cf804be6.async.js create mode 100644 eiam-console/src/main/resources/fe/p__app__AppConfig__index.22328e6d.async.js create mode 100644 eiam-console/src/main/resources/fe/p__app__AppCreate__index.ec7a4eb2.async.js create mode 100644 eiam-console/src/main/resources/fe/p__app__AppList__index.3ad30674.async.js create mode 100644 eiam-console/src/main/resources/fe/p__audit__index.8a224db1.async.js create mode 100644 eiam-console/src/main/resources/fe/p__authentication__IdentityProviderList__index.8a915105.async.js create mode 100644 eiam-console/src/main/resources/fe/p__dashboard__Analysis__index.35de0d16.async.js create mode 100644 eiam-console/src/main/resources/fe/p__other__SessionList__index.2df68fa1.async.js create mode 100644 eiam-console/src/main/resources/fe/p__setting__Administrator__index.20ad7016.async.js create mode 100644 eiam-console/src/main/resources/fe/p__setting__Basic__index.7c7fbc98.chunk.css create mode 100644 eiam-console/src/main/resources/fe/p__setting__Basic__index.7fc2a2e3.async.js create mode 100644 eiam-console/src/main/resources/fe/p__setting__Security__index.4e05945a.async.js create mode 100644 eiam-console/src/main/resources/fe/p__user__Login__index.2a52cc88.async.js create mode 100644 eiam-console/src/main/resources/fe/p__user__SessionExpired__index.6638a85c.async.js create mode 100644 eiam-console/src/main/resources/fe/t__plugin-layout__Layout.331cc1dd.async.js create mode 100644 eiam-console/src/main/resources/fe/t__plugin-layout__Layout.74b4118c.chunk.css create mode 100644 eiam-console/src/main/resources/fe/umi.8f1943c2.js create mode 100644 eiam-console/src/main/resources/fe/umi.d85528d9.css create mode 100644 eiam-console/src/main/resources/fe/x4v0w8nb.png create mode 100644 eiam-console/src/main/test/java/cn/topiam/employee/GeoLocationServiceTest.java create mode 100644 eiam-console/src/main/test/java/cn/topiam/employee/package-info.java create mode 100644 eiam-core/pom.xml create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamApiConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamAsyncConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamCacheConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamCaptchaValidatorConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamErrorConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamGeoLocationConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamJacksonConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamLiquibaseConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamMdcConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamMessageSendConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamPasswordConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamPreviewEnvConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamRepositoryConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamRestTemplateConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamRetryConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamSchedulingConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamStorageConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamSupportProperties.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamValidationConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/EiamWebConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/configuration/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/context/ServerContextHelp.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/context/SettingContextHelp.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/context/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/endpoint/CountryEndpoint.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/endpoint/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/endpoint/security/CurrentSessionStatusEndpoint.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/endpoint/security/PasswordGenerateEndpoint.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/endpoint/security/PublicSecretEndpoint.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/license/EiamCopyrightLicense.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/license/EiamSupportConfigurationListener.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/license/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/listener/EiamReadyEventListener.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/logger/LogAspect.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/logger/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/message/MsgVariable.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/message/mail/MailMsgEvent.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/message/mail/MailMsgEventListener.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/message/mail/MailMsgEventPublish.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/message/mail/MailUtils.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/message/sms/SmsMsgEvent.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/message/sms/SmsMsgEventListener.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/message/sms/SmsMsgEventPublish.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/protocol/Saml2ProtocolConfig.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/protocol/Saml2SsoModel.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/protocol/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/SpringSecurityAuditorAware.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/authentication/AuthenticationTrustResolverImpl.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/authentication/IdpAuthentication.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/authentication/IdpAuthorizationManager.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/authentication/SmsAuthentication.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/authorization/Roles.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/captcha/CaptchaProviderConfig.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/captcha/CaptchaValidator.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/captcha/CaptchaValidatorFilter.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/captcha/NoneCaptchaProvider.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/captcha/geetest/GeeTestCaptchaProviderConfig.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/captcha/geetest/GeeTestCaptchaValidator.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/captcha/geetest/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/captcha/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/context/DefaultSecurityContextAccessor.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/context/SecurityContextAccessor.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/decrypt/DecryptException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/decrypt/DecryptRequestBody.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/decrypt/DecryptRequestBodyAdvice.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/decrypt/EncryptSecretNotExistException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/form/FormLoginSecretFilter.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/form/GetCaptchaException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/form/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/jackson2/CoreJackson2Module.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/jackson2/IdpAuthenticationTokenDeserializer.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/jackson2/IdpAuthenticationTokenMixin.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/jackson2/MfaAuthenticationTokenDeserializer.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/jackson2/MfaAuthenticationTokenMixin.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/jackson2/SavedRedirectDeserializer.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/jackson2/SavedRedirectMixin.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/jackson2/SmsAuthenticationTokenDeserializer.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/jackson2/SmsAuthenticationTokenMixin.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/jackson2/UserDetailsDeserializer.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/jackson2/UserDetailsMixin.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/jump/JumpController.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/jump/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/mfa/MfaAuthentication.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/mfa/MfaAuthorizationManager.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/mfa/MfaProviderValidator.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/mfa/exception/InvalidMfaCodeException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/mfa/exception/InvalidMfaProviderConfigException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/mfa/exception/InvalidMfaProviderException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/mfa/exception/MfaAlreadyExistsException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/mfa/exception/MfaProviderUpdateIsNotAllowedException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/mfa/exception/MfaRequiredException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/mfa/exception/MissingMfaCodeException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/mfa/exception/UnableToPersistMfaException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/mfa/exception/UnableToRetrieveMfaException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/mfa/exception/UserMfaConfigAlreadyExistsException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/mfa/exception/UserMfaConfigDoesNotExistException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/mfa/provider/TotpAuthenticator.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/mfa/provider/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/otp/OtpContextHelp.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/otp/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/PasswordGenerator.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/PasswordPolicyManager.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/PasswordValidator.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/enums/CipherComplexityConverter.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/enums/PasswordComplexityRule.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/exception/HistoryPasswordException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/exception/InvalidPasswordException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/exception/PasswordComplexityRuleException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/exception/PasswordContinuousSameCharException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/exception/PasswordIllegalSequenceException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/exception/PasswordIncludeUserInfoException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/exception/PasswordLengthException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/exception/WeakPasswordException.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/exception/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/generator/DefaultPasswordGenerator.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/manager/DefaultPasswordPolicyManager.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/task/PasswordExpireTask.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/task/impl/PasswordExpireLockTask.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/task/impl/PasswordExpireWarnTask.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/task/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/validator/HistoryPasswordValidator.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/validator/PasswordComplexityRuleValidator.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/validator/PasswordContinuousSameCharValidator.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/validator/PasswordIllegalSequenceValidator.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/validator/PasswordIncludeUserInfoValidator.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/validator/PasswordLengthValidator.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/validator/WeakPasswordValidator.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/weak/DefaultPasswordWeakLibImpl.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/weak/PasswordWeakLib.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/password/weak/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/savedredirect/HttpSessionRedirectCache.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/savedredirect/LoginRedirectParameterFilter.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/savedredirect/RedirectCache.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/savedredirect/SavedRedirect.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/savedredirect/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/session/SessionDetails.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/session/TopIamSessionBackedSessionRegistry.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/session/TopIamSessionConfiguration.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/task/UserExpireLockTask.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/task/UserUnlockTask.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/userdetails/UserDetails.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/userdetails/UserDetailsService.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/util/AesUtils.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/util/SecurityUtils.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/util/UserUtils.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/util/ValidateCodeUtils.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/security/util/package-info.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/setting/constant/GeoIpProviderConstants.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/setting/constant/MessageSettingConstants.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/setting/constant/MfaSettingConstants.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/setting/constant/PasswordPolicySettingConstants.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/setting/constant/SecuritySettingConstants.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/setting/constant/StorageProviderSettingConstants.java create mode 100644 eiam-core/src/main/java/cn/topiam/employee/core/setting/package-info.java create mode 100644 eiam-core/src/main/resources/META-INF/resources/static/favicon.ico create mode 100644 eiam-core/src/main/resources/META-INF/spring.factories create mode 100644 eiam-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 eiam-core/src/main/resources/templates/jump/jump_get.ftlh create mode 100644 eiam-core/src/main/resources/templates/jump/jump_post.ftlh create mode 100644 eiam-identity-source/README.md create mode 100644 eiam-identity-source/eiam-identity-source-ad/pom.xml create mode 100644 eiam-identity-source/eiam-identity-source-all/pom.xml create mode 100644 eiam-identity-source/eiam-identity-source-core/pom.xml create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/AbstractDefaultIdentitySource.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/IdentitySource.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/IdentitySourceConfig.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/IdentitySourceConfigValidator.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/client/AbstractIdentitySourceClient.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/client/IdentitySourceClient.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/client/package-info.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/domain/Dept.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/domain/User.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/domain/UserDetail.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/domain/package-info.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/enums/IdentitySourceEventReceiveType.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/event/IdentitySourceEvent.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/event/IdentitySourceEventListener.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/event/IdentitySourceEventListenerRunner.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/event/IdentitySourceEventType.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/event/IdentitySourceEventUtils.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/exception/ApiCallException.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/exception/IdentitySourceNotExistException.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/exception/InvalidClientConfigException.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/exception/package-info.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/package-info.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/processor/IdentitySourceEventPostProcessor.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/processor/IdentitySourceSyncDeptPostProcessor.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/processor/IdentitySourceSyncUserPostProcessor.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/processor/modal/IdentitySourceEventProcessData.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/processor/modal/package-info.java create mode 100644 eiam-identity-source/eiam-identity-source-core/src/main/java/cn/topiam/employee/identitysource/core/processor/package-info.java create mode 100644 eiam-identity-source/eiam-identity-source-dingtalk/pom.xml create mode 100644 eiam-identity-source/eiam-identity-source-dingtalk/src/main/java/cn/topiam/employee/identitysource/dingtalk/DingTalkConfig.java create mode 100644 eiam-identity-source/eiam-identity-source-dingtalk/src/main/java/cn/topiam/employee/identitysource/dingtalk/DingTalkConfigValidator.java create mode 100644 eiam-identity-source/eiam-identity-source-dingtalk/src/main/java/cn/topiam/employee/identitysource/dingtalk/DingTalkConstants.java create mode 100644 eiam-identity-source/eiam-identity-source-dingtalk/src/main/java/cn/topiam/employee/identitysource/dingtalk/DingTalkIdentitySource.java create mode 100644 eiam-identity-source/eiam-identity-source-dingtalk/src/main/java/cn/topiam/employee/identitysource/dingtalk/client/AbstractDingTalkClient.java create mode 100644 eiam-identity-source/eiam-identity-source-dingtalk/src/main/java/cn/topiam/employee/identitysource/dingtalk/client/DingTalkClient.java create mode 100644 eiam-identity-source/eiam-identity-source-dingtalk/src/main/java/cn/topiam/employee/identitysource/dingtalk/client/package-info.java create mode 100644 eiam-identity-source/eiam-identity-source-dingtalk/src/main/java/cn/topiam/employee/identitysource/dingtalk/enums/DingTalkEventType.java create mode 100644 eiam-identity-source/eiam-identity-source-dingtalk/src/main/java/cn/topiam/employee/identitysource/dingtalk/package-info.java create mode 100644 eiam-identity-source/eiam-identity-source-dingtalk/src/main/java/cn/topiam/employee/identitysource/dingtalk/util/DingTalkEventCryptoUtils.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/pom.xml create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/FeiShuConfig.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/FeiShuConfigValidator.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/FeiShuConstant.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/FieShuIdentitySource.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/client/AbstractFeiShuClient.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/client/FeiShuClient.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/client/package-info.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/domain/BaseRequest.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/domain/BaseResponse.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/domain/package-info.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/domain/request/GetAccessTokenRequest.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/domain/request/GetDeptListRequest.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/domain/request/GetUserListRequest.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/domain/response/GetAccessTokenResponse.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/domain/response/GetDepartmentResponse.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/domain/response/GetDetailsResponse.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/domain/response/GetListResponse.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/domain/response/GetUserResponse.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/enums/FeiShuEventType.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/enums/package-info.java create mode 100644 eiam-identity-source/eiam-identity-source-feishu/src/main/java/cn/topiam/employee/identitysource/feishu/util/FeiShuEventDecryptUtils.java create mode 100644 eiam-identity-source/eiam-identity-source-ldap/pom.xml create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/pom.xml create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/WeChatWorkConfig.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/WeChatWorkConfigValidator.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/WeChatWorkConstant.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/WeChatWorkIdentitySource.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/client/AbstractWeChatWorkClient.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/client/WeChatWorkClient.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/client/package-info.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/domain/package-info.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/domain/response/AccessTokenResponse.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/domain/response/BaseResponse.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/domain/response/Department.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/domain/response/GetDeptListResponse.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/domain/response/GetDeptResponse.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/domain/response/GetUserListResponse.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/domain/response/GetUserResponse.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/enums/WeChatWorkEventType.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/enums/package-info.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/package-info.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/util/AesException.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/util/ByteGroup.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/util/Pkcs7Encoder.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/util/Sha1.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/util/WxBizMsgCrypt.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/util/XmlParse.java create mode 100644 eiam-identity-source/eiam-identity-source-wechatwork/src/main/java/cn/topiam/employee/identitysource/wechatwork/util/package-info.java create mode 100644 eiam-identity-source/eiam-identity-source-welink/pom.xml create mode 100644 eiam-identity-source/pom.xml create mode 100644 eiam-openapi/Dockerfile create mode 100644 eiam-openapi/pom.xml create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/EiamOpenApiApplication.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/ServletInitializer.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/configuration/OpenApiConfiguration.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/configuration/OpenApiSecurityConfiguration.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/configuration/package-info.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/constants/OpenApiConstants.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/constants/package-info.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/converter/app/AppAccountConverter.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/converter/app/AppPermissionActionConverter.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/converter/app/AppPermissionPolicyConverter.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/converter/app/AppPermissionResourceConverter.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/converter/app/AppPermissionRoleConverter.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/converter/package-info.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/endpoint/package-info.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/endpoint/permission/AppPermissionPolicyEndpoint.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/endpoint/permission/AppPermissionResourceEndpoint.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/endpoint/permission/AppPermissionRoleEndpoint.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/endpoint/permission/package-info.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/exception/package-info.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/package-info.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/package-info.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/request/app/AppAccountCreateParam.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/request/app/AppPermissionActionCreateParam.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/request/app/AppPermissionListQuery.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/request/app/AppPermissionPolicyCreateParam.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/request/app/AppPermissionPolicyUpdateParam.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/request/app/AppPermissionResourceCreateParam.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/request/app/AppPermissionResourceUpdateParam.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/request/app/AppPermissionRoleCreateParam.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/request/app/AppPermissionRoleListQuery.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/request/app/AppPermissionsActionParam.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/request/app/AppResourceListQuery.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/request/app/OpenApiPolicyQuery.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/request/app/PermissionRoleUpdateParam.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/request/app/ResourceActionUpdateParam.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/request/package-info.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/response/app/AppAccountListResult.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/response/app/AppPermissionActionGetResult.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/response/app/AppPermissionActionListResult.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/response/app/AppPermissionPolicyGetResult.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/response/app/AppPermissionResourceGetResult.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/response/app/AppPermissionResourceListResult.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/response/app/AppPermissionRoleListResult.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/response/app/AppPermissionRoleResult.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/pojo/response/package-info.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/service/AppAccountService.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/service/AppPermissionActionService.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/service/AppPermissionPolicyService.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/service/AppPermissionResourceService.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/service/AppPermissionRoleService.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/service/impl/AppAccountServiceImpl.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/service/impl/AppPermissionActionServiceImpl.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/service/impl/AppPermissionPolicyServiceImpl.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/service/impl/AppPermissionResourceServiceImpl.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/service/impl/AppPermissionRoleServiceImpl.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/service/impl/package-info.java create mode 100644 eiam-openapi/src/main/java/cn/topiam/employee/openapi/service/package-info.java create mode 100644 eiam-openapi/src/main/resources/application.yml create mode 100644 eiam-portal/Dockerfile create mode 100644 eiam-portal/pom.xml create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/EiamPortalApplication.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/ServletInitializer.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/configuration/PortalApiConfiguration.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/configuration/PortalFrontendConfiguration.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/configuration/PortalSecurityConfiguration.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/configuration/package-info.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/constant/PortalConstants.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/controller/AccountController.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/controller/AppController.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/controller/CurrentUserController.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/controller/FrontendForwardController.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/controller/NoticeController.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/controller/SessionManageEndpoint.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/controller/login/LoginConfigController.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/controller/login/LoginOtpController.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/controller/package-info.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/converter/AccountConverter.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/converter/AppConverter.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/converter/LoginConfigConverter.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/converter/package-info.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/handler/PortalAccessDeniedHandler.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/handler/PortalAuthenticationEntryPoint.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/handler/PortalAuthenticationFailureHandler.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/handler/PortalAuthenticationHandler.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/handler/PortalAuthenticationSuccessHandler.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/handler/PortalLogoutSuccessHandler.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/handler/package-info.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/idp/IdpRedirectParameterMatcher.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/idp/bind/IdpAuthenticationConfigurer.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/idp/bind/IdpBindUserAuthenticationFilter.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/idp/bind/UserBindIdpException.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/idp/bind/package-info.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/idp/package-info.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/listener/PortalAuthenticationFailureEventListener.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/listener/PortalAuthenticationSuccessEventListener.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/listener/PortalLogoutSuccessEventListener.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/listener/PortalSessionInformationExpiredStrategy.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/mfa/EmailProviderValidator.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/mfa/MfaAuthenticationConfigurer.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/mfa/MfaAuthenticationFilter.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/mfa/SmsProviderValidator.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/mfa/email/EmailOtpProviderValidator.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/mfa/endpoint/MfaFactorsEndpoint.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/mfa/package-info.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/mfa/sms/SmsOtpProviderValidator.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/mfa/totp/TotpProviderValidator.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/package-info.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/pojo/package-info.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/pojo/query/GetAppListQuery.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/pojo/request/AccountBindIdpRequest.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/pojo/request/BindTotpRequest.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/pojo/request/ChangeEmailRequest.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/pojo/request/ChangePasswordRequest.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/pojo/request/ChangePhoneRequest.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/pojo/request/PrepareBindTotpRequest.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/pojo/request/UpdateUserInfoRequest.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/pojo/result/GetAppListResult.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/pojo/result/LoginConfigResult.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/pojo/result/LoginMfaFactorResult.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/pojo/result/PrepareBindMfaResult.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/service/AccountService.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/service/AppService.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/service/LoginConfigService.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/service/impl/AccountServiceImpl.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/service/impl/AppServiceImpl.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/service/impl/LoginConfigServiceImpl.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/service/impl/UserIdpServiceImpl.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/service/impl/package-info.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/service/package-info.java create mode 100644 eiam-portal/src/main/java/cn/topiam/employee/portal/service/userdetail/UserDetailsServiceImpl.java create mode 100644 eiam-portal/src/main/resources/application.yml create mode 100644 eiam-portal/src/main/resources/fe/155.9783c162.async.js create mode 100644 eiam-portal/src/main/resources/fe/158.2475b980.async.js create mode 100644 eiam-portal/src/main/resources/fe/181.6b6b0c27.async.js create mode 100644 eiam-portal/src/main/resources/fe/393.0e12e6bb.async.js create mode 100644 eiam-portal/src/main/resources/fe/400.229afeb0.async.js create mode 100644 eiam-portal/src/main/resources/fe/423.b7e78da7.async.js create mode 100644 eiam-portal/src/main/resources/fe/501.72622fa4.async.js create mode 100644 eiam-portal/src/main/resources/fe/565.776b8811.async.js create mode 100644 eiam-portal/src/main/resources/fe/575.ae828185.async.js create mode 100644 eiam-portal/src/main/resources/fe/576.9918df2e.async.js create mode 100644 eiam-portal/src/main/resources/fe/687.227b8843.async.js create mode 100644 eiam-portal/src/main/resources/fe/738.1c5a5bbb.async.js create mode 100644 eiam-portal/src/main/resources/fe/767.e87b9d25.async.js create mode 100644 eiam-portal/src/main/resources/fe/847.f684e638.async.js create mode 100644 eiam-portal/src/main/resources/fe/891.107bbd17.async.js create mode 100644 eiam-portal/src/main/resources/fe/894.c90ee1f4.async.js create mode 100644 eiam-portal/src/main/resources/fe/ade5b70f.jpg create mode 100644 eiam-portal/src/main/resources/fe/favicon.ico create mode 100644 eiam-portal/src/main/resources/fe/full-logo-white.svg create mode 100644 eiam-portal/src/main/resources/fe/full-logo.svg create mode 100644 eiam-portal/src/main/resources/fe/index.html create mode 100644 eiam-portal/src/main/resources/fe/login-background.png create mode 100644 eiam-portal/src/main/resources/fe/logo.svg create mode 100644 eiam-portal/src/main/resources/fe/p__404.e855b3a1.async.js create mode 100644 eiam-portal/src/main/resources/fe/p__Account__index.5d34b694.async.js create mode 100644 eiam-portal/src/main/resources/fe/p__Account__index.c0a83ac3.chunk.css create mode 100644 eiam-portal/src/main/resources/fe/p__Application__index.c6e10f0b.async.js create mode 100644 eiam-portal/src/main/resources/fe/p__Audit__index.0aacc638.async.js create mode 100644 eiam-portal/src/main/resources/fe/p__Login__index.c0a83ac3.chunk.css create mode 100644 eiam-portal/src/main/resources/fe/p__Login__index.fbf461fe.async.js create mode 100644 eiam-portal/src/main/resources/fe/p__SessionExpired__index.6d66b5a2.async.js create mode 100644 eiam-portal/src/main/resources/fe/p__Session__index.0fc8f8d6.async.js create mode 100644 eiam-portal/src/main/resources/fe/p__Workplace__index.1e0cb992.async.js create mode 100644 eiam-portal/src/main/resources/fe/t__plugin-layout__Layout.74b4118c.chunk.css create mode 100644 eiam-portal/src/main/resources/fe/t__plugin-layout__Layout.7e7d859d.async.js create mode 100644 eiam-portal/src/main/resources/fe/umi.9115c5be.js create mode 100644 eiam-portal/src/main/resources/fe/umi.d85528d9.css create mode 100644 eiam-protocol/eiam-protocol-all/pom.xml create mode 100644 eiam-protocol/eiam-protocol-cas/pom.xml create mode 100644 eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/constant/ProtocolConstants.java create mode 100644 eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/package-info.java create mode 100644 eiam-protocol/eiam-protocol-cas/src/main/resources/templates/form_redirect.ftlh create mode 100644 eiam-protocol/eiam-protocol-cas/src/main/resources/templates/jwt_redirect.ftlh create mode 100644 eiam-protocol/eiam-protocol-core/pom.xml create mode 100644 eiam-protocol/eiam-protocol-core/src/main/java/cn/topiam/employee/protocol/cas/constant/package-info.java create mode 100644 eiam-protocol/eiam-protocol-core/src/main/java/cn/topiam/employee/protocol/cas/package-info.java create mode 100644 eiam-protocol/eiam-protocol-core/src/main/resources/templates/form_redirect.ftlh create mode 100644 eiam-protocol/eiam-protocol-core/src/main/resources/templates/jwt_redirect.ftlh create mode 100644 eiam-protocol/eiam-protocol-form/pom.xml create mode 100644 eiam-protocol/eiam-protocol-form/src/main/java/cn/topiam/employee/protocol/form/FormInitSingleSignOnEndpoint.java create mode 100644 eiam-protocol/eiam-protocol-form/src/main/java/cn/topiam/employee/protocol/form/constant/ProtocolConstants.java create mode 100644 eiam-protocol/eiam-protocol-form/src/main/java/cn/topiam/employee/protocol/form/package-info.java create mode 100644 eiam-protocol/eiam-protocol-form/src/main/java/cn/topiam/employee/protocol/package-info.java create mode 100644 eiam-protocol/eiam-protocol-form/src/main/resources/templates/form_redirect.ftlh create mode 100644 eiam-protocol/eiam-protocol-form/src/main/resources/templates/jwt_redirect.ftlh create mode 100644 eiam-protocol/eiam-protocol-jwt/pom.xml create mode 100644 eiam-protocol/eiam-protocol-jwt/src/main/java/cn/topiam/employee/protocol/jwt/constant/ProtocolConstants.java create mode 100644 eiam-protocol/eiam-protocol-jwt/src/main/java/cn/topiam/employee/protocol/jwt/package-info.java create mode 100644 eiam-protocol/eiam-protocol-jwt/src/main/java/cn/topiam/employee/protocol/package-info.java create mode 100644 eiam-protocol/eiam-protocol-jwt/src/main/resources/templates/form_redirect.ftlh create mode 100644 eiam-protocol/eiam-protocol-jwt/src/main/resources/templates/jwt_redirect.ftlh create mode 100644 eiam-protocol/eiam-protocol-oidc/pom.xml create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/authentication/EiamOAuth2AuthorizationService.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/authentication/EiamOAuth2InitSingleSignOnEndpointFilter.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/authentication/EiamOidcAuthorizationServerContextFilter.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/authentication/implicit/EiamOAuth2AuthenticationImplicitAuthenticationConverter.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/authentication/implicit/EiamOAuth2AuthenticationImplicitAuthenticationProvider.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/authentication/implicit/EiamOAuth2AuthenticationImplicitAuthenticationValidator.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/authentication/implicit/EiamOAuth2AuthorizationImplicitAuthenticationContext.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/authentication/implicit/EiamOAuth2AuthorizationImplicitAuthenticationEndpointFilter.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/authentication/implicit/EiamOAuth2AuthorizationImplicitAuthenticationException.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/authentication/implicit/EiamOAuth2AuthorizationImplicitAuthenticationToken.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/authentication/implicit/EiamOAuth2AuthorizationImplicitConsentAuthenticationContext.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/authentication/implicit/EiamOAuth2AuthorizationImplicitConsentAuthenticationProvider.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/authentication/implicit/EiamOAuth2AuthorizationImplicitConsentAuthenticationToken.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/authentication/implicit/OAuth2AuthorizationImplicitConsentAuthenticationConverter.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/authentication/password/EiamOAuth2AuthorizationPasswordAuthenticationConverter.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/authentication/password/EiamOAuth2AuthorizationPasswordAuthenticationProvider.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/authentication/password/EiamOAuth2AuthorizationPasswordAuthenticationToken.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/constant/ProtocolConstants.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/context/package-info.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/endpoint/AbstractEiamEndpointFilter.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/endpoint/EiamOidcProviderConfigurationEndpointFilter.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/endpoint/package-info.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/handler/PortalOAuth2AuthenticationEntryPoint.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/handler/package-info.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/jwk/ApplicationJwkSource.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/jwk/Jwks.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/jwk/KeyGeneratorUtils.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/jwt/ApplicationJwtDecoder.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/package-info.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/repository/OidcConfigRegisteredClientRepository.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/service/RedisOAuth2AuthorizationConsentService.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/service/RedisOAuth2AuthorizationService.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/token/ApplicationOpaqueTokenIntrospector.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/token/EiamOAuth2TokenGenerator.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/cn/topiam/employee/protocol/oidc/util/EiamOAuth2Utils.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/EiamOAuth2AuthorizationEndpointConfigurer.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/EiamOAuth2AuthorizationImplicitEndpointConfigurer.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/EiamOAuth2AuthorizationServerConfigurer.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/EiamOAuth2ClientAuthenticationConfigurer.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/EiamOAuth2TokenEndpointConfigurer.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/EiamOAuth2TokenIntrospectionEndpointConfigurer.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/EiamOAuth2TokenRevocationEndpointConfigurer.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/EiamOidcClientRegistrationEndpointConfigurer.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/EiamOidcConfigurer.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/EiamOidcProviderConfigurationEndpointConfigurer.java create mode 100644 eiam-protocol/eiam-protocol-oidc/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/EiamOidcUserInfoEndpointConfigurer.java create mode 100644 eiam-protocol/eiam-protocol-saml2/pom.xml create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/constant/ProtocolConstants.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/Saml2IdpConfigurer.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/configuration/OpenSamlConfiguration.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/endpoint/Saml2IdpMetadataEndpointFilter.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/endpoint/Saml2IdpSingleSignOnEndpointFilter.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/endpoint/Saml2IdpSingleSignOutEndpointFilter.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/endpoint/Saml2InitSingleSignOnEndpointFilter.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/endpoint/xml/AssertionGenerator.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/endpoint/xml/AttributeStatementGenerator.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/endpoint/xml/AuthnStatementGenerator.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/endpoint/xml/ConditionsGenerator.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/endpoint/xml/EncryptedAssertionGenerator.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/endpoint/xml/IssuerGenerator.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/endpoint/xml/ResponseGenerator.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/endpoint/xml/Saml2ValidatorSuite.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/endpoint/xml/StatusGenerator.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/endpoint/xml/SubjectConfirmationGenerator.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/endpoint/xml/SubjectGenerator.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/filter/EiamSaml2AuthorizationServerContextFilter.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/package-info.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/util/Saml2Utils.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/idp/util/package-info.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/java/cn/topiam/employee/protocol/saml2/package-info.java create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/resources/templates/form_redirect.ftlh create mode 100644 eiam-protocol/eiam-protocol-saml2/src/main/resources/templates/jwt_redirect.ftlh create mode 100644 eiam-protocol/pom.xml create mode 100644 eiam-support/pom.xml create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/UtilsAndCommons.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/Version.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/async/ExceptionHandlingAsyncTaskExecutor.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/cache/TopIamKeyGenerator.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/cache/package-info.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/configrefresh/ConfigRefreshListenerRunner.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/constant/EiamConstants.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/constant/package-info.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/context/ApplicationContextHelp.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/context/ApplicationContextHelpInitializer.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/context/PublicBaseUrlBuilder.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/context/ServletContextHelp.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/context/package-info.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/error/TopIamErrorAttributes.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/error/TopIamErrorStaticView.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/excel/DataImportFailReason.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/excel/DataImportResult.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/excel/ExcelUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/excel/ImportDataValidationException.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/excel/package-info.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/exception/AuthenticationException.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/exception/BadParamsException.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/exception/HaveNotAuthorityException.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/exception/InfoValidityFailException.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/exception/InteractionRequiredException.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/exception/NotActivatedException.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/exception/NotExistException.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/exception/TopIamException.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/exception/UnknownAuthenticationTypeException.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/exception/UserNotBoundException.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/exception/enums/ExceptionStatus.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/exception/package-info.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/lock/Lock.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/lock/LockAspect.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/lock/TopIamLockException.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/lock/package-info.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/node/BaseNode.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/node/NodeUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/package-info.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/preview/DemoEnvAspect.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/preview/DemoEnvDoesNotAllowOperationException.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/preview/Preview.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/repository/domain/BaseEntity.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/repository/domain/BaseTenantEntity.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/repository/domain/BatchBaseEntity.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/repository/domain/IdEntity.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/repository/domain/package-info.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/repository/id/SnowflakeIdGenerator.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/repository/package-info.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/repository/page/domain/Page.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/repository/page/domain/PageModel.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/repository/page/domain/QueryDslRequest.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/repository/util/BooleanExpressionUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/repository/util/QuerydslUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/request/ParameterRequestWrapper.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/result/ApiRestResult.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/result/package-info.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/snowflake/Snowflake.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/task/TaskSchedulerRegistrarHelp.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/trace/MdcThreadPoolTaskExecutor.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/trace/Trace.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/trace/TraceAspect.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/trace/TraceAutoConfigurer.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/trace/TraceFilter.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/trace/TraceUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/trace/package-info.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/AesUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/AppVersionUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/BeanUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/CertUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/CountryUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/CreateFileUtil.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/DateUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/DesensitizationUtil.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/HttpClientUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/HttpResponseUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/HttpUrlUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/IpUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/JsonUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/Md5Utils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/PhoneUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/Pinyin4jUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/QrCodeUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/ReflectionUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/RestTemplateUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/RsaUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/SpelUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/StringUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/UrlTestUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/util/package-info.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/validation/ValidationHelp.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/validation/annotation/Phone.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/validation/annotation/ValidationPhone.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/validation/annotation/package-info.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/validation/package-info.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/web/converter/EnumConvert.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/web/converter/EnumConverterFactory.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/web/converter/package-info.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/web/package-info.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/web/useragent/UserAgent.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/web/useragent/UserAgentUtils.java create mode 100644 eiam-support/src/main/java/cn/topiam/employee/support/web/useragent/package-info.java create mode 100644 eiam-synchronizer/Dockerfile create mode 100644 eiam-synchronizer/pom.xml create mode 100644 eiam-synchronizer/src/main/java/cn/topiam/employee/EiamSynchronizerApplication.java create mode 100644 eiam-synchronizer/src/main/java/cn/topiam/employee/ServletInitializer.java create mode 100644 eiam-synchronizer/src/main/java/cn/topiam/employee/synchronizer/configuration/IdentitySourceBeanRegistry.java create mode 100644 eiam-synchronizer/src/main/java/cn/topiam/employee/synchronizer/configuration/IdentitySourceBeanUtils.java create mode 100644 eiam-synchronizer/src/main/java/cn/topiam/employee/synchronizer/configuration/SynchronizerSecurityConfiguration.java create mode 100644 eiam-synchronizer/src/main/java/cn/topiam/employee/synchronizer/configuration/package-info.java create mode 100644 eiam-synchronizer/src/main/java/cn/topiam/employee/synchronizer/constants/SynchronizerConstants.java create mode 100644 eiam-synchronizer/src/main/java/cn/topiam/employee/synchronizer/endpoint/IdentitySourceEventReceiveEndpoint.java create mode 100644 eiam-synchronizer/src/main/java/cn/topiam/employee/synchronizer/endpoint/package-info.java create mode 100644 eiam-synchronizer/src/main/java/cn/topiam/employee/synchronizer/mapper/package-info.java create mode 100644 eiam-synchronizer/src/main/java/cn/topiam/employee/synchronizer/package-info.java create mode 100644 eiam-synchronizer/src/main/java/cn/topiam/employee/synchronizer/processor/AbstractIdentitySourcePostProcessor.java create mode 100644 eiam-synchronizer/src/main/java/cn/topiam/employee/synchronizer/processor/DefaultIdentitySourceDeptPostProcessor.java create mode 100644 eiam-synchronizer/src/main/java/cn/topiam/employee/synchronizer/processor/DefaultIdentitySourceEventPostProcessor.java create mode 100644 eiam-synchronizer/src/main/java/cn/topiam/employee/synchronizer/processor/DefaultIdentitySourceUserPostProcessor.java create mode 100644 eiam-synchronizer/src/main/java/cn/topiam/employee/synchronizer/task/IdentitySourceSyncTask.java create mode 100644 eiam-synchronizer/src/main/java/cn/topiam/employee/synchronizer/task/package-info.java create mode 100644 eiam-synchronizer/src/main/resources/application.yml create mode 100644 lombok.config create mode 100755 mvnw create mode 100644 mvnw.cmd create mode 100644 pom.xml create mode 100644 tools/codestyle/Formatter.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f76a9a04 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** +!**/target/generated-sources/** +**/logs/** +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ +.DS_Store +*/.DS_Store \ No newline at end of file diff --git a/.mvn/jvm.config b/.mvn/jvm.config new file mode 100644 index 00000000..23adc966 --- /dev/null +++ b/.mvn/jvm.config @@ -0,0 +1 @@ +-Xmx2048m -Xms1024m -Djava.awt.headless=true \ No newline at end of file diff --git a/.mvn/maven.config b/.mvn/maven.config new file mode 100644 index 00000000..30f8325a --- /dev/null +++ b/.mvn/maven.config @@ -0,0 +1 @@ +-T 1C -Dskiptests=true -Dmaven.compile.fork=true \ No newline at end of file diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..bf82ff01c6cdae4a1bb754a6e062954d77ac5c11 GIT binary patch literal 59925 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

dy!05UMhSKsK zV1FiJ5SlAhkpcl_H0wRzql?0Qp5wz72o2cMC@utM(|&o0ZO_JpXr+N7l~F?Ef_02md^m|Ly|(EN; z%;)3t6SWt{5hgzszZWS1v^AU?`~Rctor7%qx@EySW!tuG+qP}nwr$(CZQHi1PTA*F z*Vo_ezW4q*-hHnl_8%)^$Bx*s=9+Vi%$1qr5fK%c+Hm4kiE$B;kgV)wam25w$Y7#k5$> zyB^6k3i~L_6~PX554`c3Lxx;&_sT;I^U92G@fS6#(Xv!B%;H3+{e)1R6lyU)8AK1_ z?@>F5H=sXG=ep;kDRZO_ofS}`Jus*Qp3`_V4v~&b-RQ=t8AN5H5{@!_Il~0 zZd!-aH=h)(7CJ&tL%%{P{6d_g=5tsj%S3Z!QxjrLdjoKmNP-zSjdJ!?qL(UMq38ps zjKSz5gzwhDFA;5md5yYb>QN)U_@8Xpjl4yw5065)+#MSGp;yQ*{%mt>12;$~R{eVV>o|juO{Z^ z^o^m@DOBrE2mm1nLgBfA(Wi=X9R%(1UYZcZJ!3;*bR^smI~6lyn`O4BOwo-STsQcyodVA~leg9`{=l(qDl@DCM>s+w`%S_q*PIjYP ziuHHuj0VVW1%+TH*lx9#-$^q&l)G_ojju-w{# zVs{oOc>_fcS51xY+19tN`;V~R0wVyuxdkS|t zC}~Gtu-UyA{H5~6*ocUWM)RfQ076mL1r zFVWV%zx!_*zk`5&dFbdq4nbWxIwAu=`+$V-`m<*-Z*mE2X|>OCAJVV;wlq0E$hVe@&x7V(!xg1*;%`} zxxBu5;jmZEH*e!Rj=Mz|udBR8BR6LiGoLWb<1=<14it;Fuk$6=7YCR&;F+%r`{S6M zP92W>ECy`pZR$Q<6n8Zw1|uh*M=zK=QP0b38_aX#$gB^y>EahIiUzy^MP1ct%UhZX z>FFLVJ=H`FRSq!<_DtWyjLZ6t^Nf|?<69Aj$U0*lrAJG0{t;t8Y^SKLacoR%3EXw+ zDi5T^PkjmJp7@B|$lkEwHHaQ7BGc$})@qNRqk4JH!(bgPM!{Mb&Kz|UGk?QskODW5-NCJ3`Fbks<}%TsOB+e{Hn1i7BP z(XsKkfl`r0N)u1VqaPYGlDxR3>%y{&vYaQCnX8AAv8h8>a^4<#jAhtfa;TdoFlN=?Ac{@Cdxj{YI z!kxobbr?~GU8JKwH2Ywa(#i=Rzof$nu?4-zlN#QJflTO^QkyarxNI<~MY1}jy~Jz` zBRwV&0+G01D9biQ4PR*1NiSqTXZB~NdI6yVEU|AiWJYA>k9G=*`R^VFjr{jhqZ$&G za0#huq)Mhb&8oR!jrv%;xRe@b&PWBXh7ATurhUY7yobngzP;($8b5g z9U{5JMt%fMp(N6ZVGsYa2p(#ry;Y&;GG(DG((_GrS%r&waWuX94*RX8>&x|Lzv8WCaXaWo(3FK=U@G#S$8kCX_R6q|VO;WbeXk~x zmq?NS+S2WfO|{j{dKy5``SRA!r+%)`DCW{s?8uZJW{-4%x}KJzAtiyY6b#)!fe0kA z)=W5C>X6ZLRFH_-$)Z(B8Hr}FD#FLGum2gRluDsrJHf$do$r!ORQqrI6~=-H0vPiG zC2V88MIp?Xhc&UnIS(c)naRXTu-r!%x0J;3uWjp5K%!b_v$;;T0*{_2txs!*+BgP} z%eY2;N7AFz(g@fFy&(hWk`R9#fRZ&X598A7xjHyoDJ4!3CK{Grr4>0bTBw3ps{tN7KqVY^)~B5St2NQS9wH_Lc=s8$1H5J?52_$nh z+rnm{F~bVIsiCZ^Gy&eV*X9JTJZB^`|6F$9|Fq@ekZKP~h_BWGsow^hUpo~MCTrdk^1B;= zNXiYAZnUPm>}{vX*&Yb&{0FNvW!V)h-<{na1yT-|kAkG7xU7QA-NAc|e4Nf2`OWnV zxbr6@^wO^6xW+Xdu=Z{sdK+Qw3Dii+X&Y(VdCv>CFEIOt?MCM?9@CDUKm7+N>%!q z$WI;(L@2YJ&Qfwr7k@<77r}%_q3O8c#><<+(JFdeT2?e+nsP4h+`n(HuX8^8qLN88 zv^9`|ICnNwS^PYDf7ebCGG~QNosD6-%$5;6Yx$`PGlZVnxs6ntftJW^L?iy3KIBDW&1q;{OspV)`a4w`+K45XmW5g6HLPL(lu zM^>HAPux}=ZJ?|;f=zDh!2|)WLyu7pHcc)9vAr(R_-sI`3GRfExjVpYMgql~xox)Q z)W3=WFT93oMdC)bluYO{cphI8Hjl&)W$TKN(PAk2r&mB9-)@%@xbewYx!c z{}phewJ939{qT;q&KR_!>>XnVYPC^kRaX%+G_v;*kg4g0jdi&G2G5$4#bk+*0mK8` zie_>y1oDA_0hGE(n`I(s0k(P&;*KDaX278vofbbNMZ-&1MCmPD*6d6oN$VjMzpTd@C8e zg81s83_+Y#T;duYQ%tXE$RWVk=@P5Z1VY<1C?mU)7?G9IHYx#rHCx1Mhb!ajXBoJ-rANULXqSAu0Mn9s%@_;uy-AOG|5#jDZ3j5dR7|< zR_{f>x5E@uRa$=rDD-yel$t(bf5=#v9ZWObAu%fou?4KkV-kvjmRiGX7iDe(Q)_^=>m}`2$#Xi#5CpJTi#5EF1T1mmPB}c@A6ou~a`>sHSeM4gF(ksh|DObX#Ao1r$Jp3I3 z-#zhd+d&)DO54E0K@@kKgxRB5%x&3BZ$OrawIi6~b_kN~$5G(kH6b5BD&%g70UWu6 z-ub`EccvhA2YleM%U@;V)N{Ixrkd0bjN}m=kn%!g%wE&P@WcBs>5NJ~t}y$Ar7F1n_=iC*<|&`C=qG#+ z0|)?s_kRK(@&?Z40!~gQHirKa2ua%+8CVNj{J7LD3|*Wp?EV9bZ1_j%PH`5U;9>aTZzwPD=a zXur{4zSk&)HrOFOmSK8ZKMHdg*HQk|a($OZ(0puje1K8EZNjPavWjhh64i-B(p7Zf z2g`IQ_W)I`lGa!LCabrDUSVPmGZbVX*#xhnAH|koEn~hs`=w;zVM^IEU${9oXf4C9 zk#|zrR`2_TI+u08MszOoi%H;viD}|x@Ax-{F_aW3ZIQHw-pT;hgNi%weuhcB7xt*kubK4fep+r)eaJIl%p9|sqv{M(E4lgwXe=HL2nYvO$$HX>QpPxqUn}WG zs*l{rztHOO@k5#cP%_alezmlZW9HCcT_;auQpbtV(Kh6e(9wF`C;OM(L&uqUaFglN zk@mRfKGV716J9j|zU-6W(m9pmEF&sbiZMv*M3~8lC~<@%sH8mKCL5zS4h--)TNbi$ zGT~m~}sa$tL(& zG_GBAe(+OZUY}-iY-rcb4f^fNZt_IXS52F^MC6>C?-IuOUttpxwVQBy0~D@|I1g*pQ^8D9@mu?5(kge3_GjbOm2G+7-z zkx`X#L5jF0+(b=RSgOE*XGFk$mF562Yft^UFH0micC5KNH~tfuDq*ce5Q~fKPyieC z9su^F5Df-F2X&FrZ1?<8uQ5h`uh~m z=&m+g_sL;h^%^JcRk%COiklbyo`Co8z9C%hj$&e+^pKMm>7Jt({+@)$DJbC`QjMHZ zi%3X-hLW4Gca)8|Pf3A1t4Ud8Gcj`ZNDE=lz<+3#C9z0jMR_q934+6jFXzJ$uCq~+ za-#O3p1hSU;tiKizC8=Mh@y(Ne3L{f0B?%ewopC*gCiXqueXVpGg9HaGK>hK#}F8++%^d7M6b=5@V(e#PAgrUnD^4)b1JPZ-PGNWqckW?kadj9w8b7f zp6l)!4JIwHtcBOekEW-B`yJ(E6n$+g06FFIjgZzz&+`UpKdgY-=lxNe1BI|=Cg;T; z?FYQs{*)^&tV>xbx0m~jf7l5>`+q#>!*0u^UJNZmE(3w>j|yNHB$#6zkjE;_0pL0S ze2gb)=zGHVUt5ge;3k7XmZcc5;mh=#z-ZobkM!xX0De$bw@9s|&m~zN9 z!K5tX5=4qA2sK|$bdVMz5etUdXN!`}2PL8R7qLr)Si} z!IONdCg$e~UlJ3u{n50K+;kj7SP&tC(^xDUbl{fdvL#ilA93{7Vm|&0)1p+nx=!XmT2qv6B?FjPHZV*SamC-ro9lXMAbWtsPx?Xq1Kcc_^$@r-YuI4|#Q?})HOyhMfBUVTIsc4Su?*`>kGqVs(0tbI_r0@mbv4tR&NZCQd@%?W!R_Br)qtk^~)!$ zd{bZ$2k_tV&)c$dz%vTer6*=naysJcAnpE2vboBzhwzL3ZZg^xE_1)_2eUw2B&FcL zW(!+zg@=0oy{=sCi##j;)Rn!Ty7I5A;QytP@}FjBaRXc9p9bUK6(&VZ!%ayA`L8Y0 zHgiu1Y%~0(WC8`wPF)OYDg?-xhpK#kN37I*3t$V> zeFT`E`_n>;_dQuVYN1PBmZ_}9TfEcl#^=`Abh1!Ek&ykSp^2 zUtg|J2l-(Fu4-@Z^fZW1~i@QYwP9Q9$d-lN6U6i%K#778wN;pE7`?CIfN* z4j%4F^H^LF6Q70%gi@GEB7#Kar{F)1=Hjc!yt?q2&-sWb^&Mo@Ali3 zYsI8ugwjs$rA3@sca{d2=a5mZ6PM=U7R~l1{udpZzpk<&^i)W$IV*$FUzyJ>#@G4l zunDZP3O}4G8=e2)DEXo;q|ooRSY*pQ@?dPnSA%LBmzMuh zj6iCX{hWsksbMQPykb&WEA^2^)4$ly11z>xG12rAj}?8Ft!(tswaOoNlpt=|kqrTJ z&?vxxBG>4bNn(%_w*|gVh^|*LD_=TzvKLX^EG3#)_JHhIOGSwPo4|0o#`B(-!+g_f zebxHKe=60kQz4i3=g8Q=o!~GyJjpp(m|JFSl$~J?ocx92m&&RUW=F?w)i?X8sjbbg z0+7xvpM&&Mvk2s6TEQh%-l$+wW+-wwx(yPsAW>CS<4@5r)9$_e^l&p0?yxh8t`Ni| zvkg20%R$9KD0hWHDff&(!UL3EXA@7RAORZg2_v!tmF`q!lSi%o$>srm>6H|S)B^2X ztV|vT66Q&WzEYv3LCrtL@fFVn_1u!3AIwvi9c5g^-LY)$kEOwFcdT%;T!@=Lh3b{K zJ5DKC5TfipAQ;Xelrj5>A z=_T7N`9+b0vmdY_zM3SwtpmRY?wNX&N^VG?5}z__+A;qz)l|ZX+QaujvNXdiXZ(V? z{OmPo1P@Yd;$G3ic^NHAm|1j%cIXFahDM~236V%gF?}nu9!H?ApHB?XA?IZs*m$xN z6e^ufgCQ0+_=81#=-f_IGbvy4Xizg)_Q^<)baO)G5(DO zgxn}JpKET9(UqMupTD8jB3cp z4G`IGH%ByG7iZ-QD?Esze`e049rA`qU8-l!$qPyeHl#z_q%CNdv(L)XI;?Ng4p}qk zjkLr}p4PA1I;7{Kc1WJp_Y!Q55JqK#sB5nY)=dehb&d)~g=roafxSw>Sbm)`xVXcf zG#`10jAW<8I#Nd!Q<)M`*0YE;dZ$(eKex&V5$dNnGAi-clRskp_SX#aKy?8;Y^RA; z@xEcdlr!iVGK@89*}AMBb@T}NL#V3*a00ErFr0GKMbDa2oQ-DkTV{N0Y_X9!nY1oWN1B)$PK)1Hfas5LPvtlH8ZL@g6sQ;=~> z=vTK;Y5TAt=ya36;hG?pES_n__RRVv!qlpCcy$N%vN$cm%p@=41Lzl*;2C>KsLXaT zT7L{$DZI@k7u*!SE|y2=Df|?99>gyrLB^ur~Y)vi9TpSJl6Z57d+o)lQAdh`R5kMGB7)eE`*Q;2G zQEcRN!Q?$b+o zUoag8iRTMmKuJ)5s&zS~S*B1~zU7tUT|q&h!EInBeZf#vwR|05>zpU0zRe0VWg5C; z+*3eGa6)oAS)jk-xN&bD5&{yx=Oh{=T<=akX4F4Yue*V0VM zkH4;7TLKmx%@)s6c5z_Q&5qaRX;$2vIP-ud)H84PAd0uJX*ee_AkeYKVtI6CW@W(9 z8KHRBux28|zpfOJu7mRVm*s z%?_&|3rLG%MZsk-XuimeAl!(zkxHX`$uQhJ=7%bztEXtmw!ImA{G>b$_T&F%g zFsQ^s?i59_UX8n_!c>ZltM6ABcMHOtRyrRBB3#Yo+AYyiYjPIXgd#0RF$%&xX*?+- zsPtBuy)cPjVkYkf31o50Tp3zUe-dekc|5FYz`%%l5L^>Pje2fT{!AGEHxWG_Yi|{!_@x>cc6%5SD z$ZvA==C5j@X;L3MCV!XA?SG9M0(T#83W28(9aS(t{d&siNAR`PZa(ke>q+Bbo82ut zvU5xmnR~F1ffCpw7|Fg1Gx@$)QGYDzf$|nfH3sKP3=Huhz#4)dH-ay~7cR-ML4hxY zJC3AyNh<#3hBqDyFFY{D#*eE*cnh{slzoT{|2On)ATR!sO#t-^ABA9?$(s~V<1UDq zyo>|Hc*Nrxk#`IYFkXaDTnoHWAP3E#`a^&-`SJ1RcPRHkeTbBZ&q3G_0==kIKNsi8 zPK+SND@w;5@(Jm9!|;LDkth-G0@RZYW&YJ3k={qg)_?xtrkih&RnY!V zo$Y^|7$WW_MlSzvW>1PbggdqghA-L1jCJc$kjxUIfuHEPj zLAS_=)=>DNjluF!EIspf<>8IN^gzw?ak~<)+k{ykeXo%GE=68f$Z;ZaxUAiN%zGF_5d-JZ0I9JZ*6=&gi*5l3i_WA7VrU|K{v|a zF=S?&Yw?$7*XrNDug-5bH}qO#ji37gcoNsG74BAO>OHL zJ+$W5wVs^^UjrNk2QiwyJ(aXP&FiHZNvXoDgPCs;lE0r3q^E zb1QZFSr@``4tbojlnOSCOUjP5QW*?2!?w1>p3YwB&Mp*GO3M*qgz>{jv{ak$b7(E?tkY*+R+^&>> z2dO%o%W=L!QGyw(WuAnw#oO{!I(8KwC|wq_y)<9lMxDiZwL#OlUU_DnD8&!tX&a7f zewQGgB8{dwkjR8EC%AP&bY^iirN#jA47*}#6?~g6@a?%^7(){yv(mgF=P`2yXr$Ab zuYEY=Rw^DeYTFZ^Ywa=6!`PU?q?O*FI=gFl`bbPev2k8T+=C;_X>sLJQt7BpOATpg zrpfyxa?;Uc`KUT2B@@q5dI0rCDDr{Q8d~En$h%e_rtAvjTEMd-OH%Qc7)o~}(R!O` z(i0MG6N^6LsC174qc^gK-0ayYDy1n5!q9mg_|@<( zH^wGhrdBV;Qzf}LA3=l3S|l{2(ylqgc3&K7pj~tzGSA`-wO86b&05pv_SO)Zw_hfmjx}wah`^|Qo(J(X2h!rc zPxx05-j4zshLMr@l7%0`IwPtjmgCwA{Sxj^m0H$vopZOcn-(l18gE{v?!K>bbY!=G2sL;OsI!wlS zl`om0y?Z#6@8vtXFRh`e5wNSy>T)H41%)Nt*jt9t?c#B>nBknI{Kbhq*5+Q8Lxe_H!J*!N? zH;Gr-bx%ExZEmt^9#)xcGN#!|?Xz6|l^~v7U7wM4&5cAIxbMj53pOBXW2LxqE#=+s zUC(EG;8)Odp&Rd)Qg_wrCnDExg_o7dmilm!?}lv0f5NK>w#Db7WRQa5Z94pw011GV zyHnjESKowJ&H%GT#al{iWgq|S`7S)99~4MXM?gl`=`rD9WWj$*)*NbWq$x&Jdq^ z(Q<+*Sx9NqE8$^Fqc(bfoIHwRM8##C@jW61>q;vG-*gk8G>_$;P+4b&%lQGl^XQpt z@48~+y!wp4mqN@Q?HOZ!Yr_;kT-E1R!Dz4OldNG)t;&2^&}q?~dMa&r60E7E)}#>< zrV*SWbim~#un~*J_!+nsWF_-x*9gTk>Hl>g2f7!ZQCMExX9omA0+-Fd%?Ek`^u5Av zTse2a$3`W_+4p=xIbdWKo>d*OlH=zIocE<>kNpS;Lx`OQ&-Q1P$CASxn1-0~RGYd=l#b>XT!xg+7u%F$Q7jSakj)eTa>Ty2qji4Eb4HFzvHy#qP|SXp zeb#Lbt?Nt*I~QuZr{s3Gk%GGcNPV5a16K0EjBCtb^pLdk4E5uLHP+1tY@v3z5hntx9$Vv0Tj2xkovNOuQz_TE%+7VTio)we=x|p6Zw6woNPx zcG_Z2O%BbGxfe9ld2ol=fLGR4aFV*%y*3D#mSjOJI|7z5B4+&ACSoxT&RK_fuBkxk z1Z{D-MxPSpq+f$DN!oyle^-|TkMi;fqFJ1UGd5NFA{AM^B_NurnPV??jj4yDq`QF! zXQ%rlV=SedtGKM5GccN+LZ_zY*nRh^QhVnOGA2jgF~DjqY%>eUXu}5pt)p9N9V|0Q zXC@$-8kj_9y)dSR&f2Q-S$t*V60-4m5IfeHAp)(*?%V*RU3YRI+fVm;XbrN;Znfre zHV>~Kt<08qOPU*d|3s=CmW8uaSX^bMnclwZa0*-JYD_xdlH-9QSVqCTFRD6%n}VS4 zy>uY+r9H8?BwSa;PMf%#`x7lDq2Ra&?)MJ=q&X-Vdw3kLg=AF;bh`Ngu`{SU0AP{2FA1bXzI)&Qc+N zQe2V^EkBDVUja~}gLyF(bfSN%OWm}6u4HUH3r`v7TIiEzS4!DYc1O$+O(bDf_b(zmfoP2*iYBPA-5lKMee z{!TLNugW*re`hye;8u`de34Z~ks!!LT7(P~?WfwY)j%M(rRlsVfY75wv`_j8-f<~Zh@@_No5u3lgB08$gw3J7t6YYm|-P>#mI z?Ihgih8w9<&jhN0?+L@xpaZf^v}|(+(B!Te$gx^{k_-y^@xZ8pvz4Teo8$&XcRy}gCz)E#b#7b-MxVm-OaCXYoKRhcAIJfQDELSMoUPZ2A zGJT9WYcGs3O6S~oE52|3o?hBGjTo}Z^#p~Y8HA5Pg?)uzq1dK9(?}wqZwRa130=%H zYf~z=E0yYqfTG0fyWBEMhY>h2^w4T@H3nLOIgGoExay2GP9=7H+(sF!>QtGs1-g&W z_gbac+_K^zlCn7G0blgrvHCKoOxX2B-RbMlZrJ;wg{CYdkQ}uH=vCz{^XL9b5MT@I1LRLBCN2G_*J_s4ZGh zWx7MbR#kfA8X5^2SsOa1ssX$FKr+_smpYMtr_8IC^|BTXp$X~a|@aOR`r7XM(DK=Ni-`62A>;$AvH z9_f{d2&YCRYk$@WOzak*c~OoAFfe6f@DJQ(UOb0(1s-V6+8}t zM%Y6TDbM(n0`0~e(Z=fVgsQi^OTtAv{cQHYLACfn!I5^C`4kt?8a_m$6 zbcTozSL$v*0uQgb2#l)xk-#q3kt{M?g;oWD0s&KKtKIf|mIluc_x>!Nn=F(UZhmoC@MLVWfWf8%A{!LJ-a9ibm(5(&roPX(GX)q zd@M1x1j~Z)riLkJ6l^njEwFgGs7mySZY8C9vkvltS$4KH+PxmEb7GD8$Z)quJ$36>!5YC6H4?tWLx3jX zL_~2klDHUK>j@1}T+ZgC#@^9#==euU-lRuP-UC^5Cc+L8jCGOV7-{#UL(6{hSs1p> z-8|04uLdI$1?;BBEEg_BTk#KN4^e`X!u!4==E(^tnRt1KV|!i-9k}i*QR9@it-?e5<6jq(E{}G5amY*n+H0gn_Y9 z-8;^pTZ~?CK_9>Yi%5S(q=#!=vps#u3bpC*N25|FGH$TQ9Pd_4r2%$YW!S{i=_C!G zD_fX}hHLaDE%xg_fp|i?KbzndD++)5bCZZKr8}JL`2AxVDM>tTh|-T>%j~EB_}}&( z|K(H^a5QtVF|l}x|sSOHm@dqAK_|9T*4ARfIiVq!E1 z{?^1IHFL*xX$M4a3Mm5YU!EpeD1oBkARcKhJu}}&7N2i-A0U4zc4~oNFEZ@*1*d{J z{!TQ-;$6U&WxGgOjF^lV^S+fK(41yMfFZe${01$COSKm>OdY0Ko`nRwC?nIcv5sS48^fobUN+7gD3h<@?TK=U zsq2}1JqYJDkDjs^)6H3!Y^(ni&NTu{w6vfAOZuc(I-NvUIA5QH9(Sk7D2hx zNiT)h!1lkZYyV}v{?Q|*B<@K93LuZprFU9Oj(?x*`7jTy!&B9yOv zBC(n=8x!WoL6TsFoU<~Hlq~@JoFJC(_I;+4<3?2gkpWZU!T~EWMF7v*q|26`QcQ^K zyY7tY=WEzh-Beb}LTZdzTqsr?>f%%?W^OSKq2qcG1lkqAukEF_zkk$u>XCWe4? z#Ea%vy>ICg-GEoSljel7W)-xQqU;Q+>#pyscZDYnsvo{+1MT9<8T4`~uVdxf?M~|B zynet59NiL z!rIjSxz;b%7{vy1l_G16WSgRE^<nid77&vHB`Hc!j_1F`ZD`0gi18)_8?o51 zU@6a|ci)iO?`1pg1#z@MGaRt#+VAApkLK*L@84Osn8n1p&wayu_RhR=UwwK_{XRd- z@_u3Wn-N%#fS{lWoezfKS`U=q7T4pO{SIjeFQMNZYxLGubs&kZYA-$P^!^hNiAC_F z(&Wq`HKids+xS2b*p4AAYkL|*f4oYA(x!rpT&_C7K;2ZG?{}K&D<-FkT@)`3VJ0Xb zH#wfssnie>s1svHRy7r9dzwfw#yY({tYB*1nNx)vazVXK$6z6(v#cyYmxjT(-pz)Q zmT^!`Ze~41QiQ(6|xf}+@C5ZNKgKywZ9F6&s&=xLzP2GjAv3Y0oF|N9sQ z)#f|e$7y6jIc&Qc}%ut}8+Yq?|zk-iAB&`7zddtXt^a zODQ(DgQqHOTe)pS1jRV(Z4SSYxFFm9bj`YffOXR_nrFrf=Pmfr^F8?NXDAH)RY_IJ zia@*!T}8>IHGTVN@d71~NRP5^{UuSEQBA;iP@E>vHBrii=Mt#3LM<}6v(uCW8I>pj z)iuPfGO41XkYTVm86?P+ZI7a!bu#F#q8E#ld66=_3qe5(7rwYzkyP1Cj<^O27m+O1 zqSOMa#3!)|Oi}&%<#TTC!j#90$`EUJWnuAw(DgEXbdGZ}D3-~lWKfV3CT06jARCpc zgW3?!cGxC<4bPFx>G2K|pQw6%H=mDNJ9f0i7Z9 zM9Op2T#uZC_CRl%l}%9a`x8xq0TEG6nyJmw%8@N+>W!pE-tgq@Th2AO(m( z5h}V(JEs-EqPp`)cKevppHePn%`Qoa-TTm}v83nfYu{=X)eka!5~;S>wiZ9KJjMq6 z>Fgx8lpK|M8rEmK1%a_jTLUsb8vpPoSY+$7N+_;3vCrkzy8E~s*E6qfhheM@ zrP!Wm9FgoRV70zMFupOPdouaMx%rka;9iusBffkukbq&Oa!Av$T*C5wgjUDJqJ6aB z(?h;NzQ4!^wA4Jl_hYZYcSg~3H}db;N0wk864a3n*J6lB-nb)I+5y2n+93^b!`=_} zy?b!&O*YX7-^{Ztu`4-1**M4EM4h_wU2-D?C}Aqy5ML7Yl@D#`Ppq--or&5LPqq_} zTx|N&G1%{D- z63FD%(!Xv4BFxTlU%s)bFl{J%a)l zqbCh9*g7WHB#?5O@r&ddY*myj&i_IQQSRbI!%jx#TIh8Iq)wt}a5M>>xO${;MLFTF zQ_O(@DdX&)d|+07Gko>hSrJy|%;=1|&mC?0hPHtn%4a35agZa4ED#_egj-4`fBqo0R#9mQ#BIn&i-6N6{L`Zvuc zhVM*t=AS0*G3(^>#-9WE*H7jAAN6DZVp#r5)s#1Ibo$Ty%9LoC$U%Pi5WROaGDy=C zPt+z^E_YxBba`ZMfei{n!7?uADyKFLcYluL^~1#!m1QqvZ}0E6J}Q3>QHVrfykO_w zv$|82jDqR3+Dr8`t0^fspZL6W?}Nb;in4>0ln_bv#S{!mP!7LHENN-l=~@%6ujbu+43{~BuZ zw^SLl6$KJ<_cuxbNb7Q!O0hDnWC6M4;8A_GNy9bkmdF>;M}Dt+#2h+{u6VQ^>0eSK z?k25<;(Ths!zu0AKiM3QGv1%~7fk+3?IroYB0MoYk(mh#@FSK8vIjI`ov_bH&I$oz zrLZYtsUQX0EBOWR#C}5l3RW{%Bo}~%2(30eRFFehtEwIkdu=PDTFFsev{oQPGaF9N zLO7CGqMw|o4 zXEdacLL>~Z9Q8;+O$?#CmfUc5aG9?YnHuPISSR3nZ8JM_D8dyb$SQv2-HWX?N}@nm z^pSjPE?!b&xN4pT6Iqj~IYUn!w~x*r*YJ!DJC8qDd%4PPqge{1d$*@GPtr)Wz z>kkUX_B@U^7XN4)%$HV&YAuDsY&6oUGVU~47&0HNr6)8$M29v4AHrT6Y7amNwe@2$ zMSs9J#(B)Opvkmq-rs#zH^A-}z<5I6p~|}zU3FOP#3gE}fPLjmm(O>k5}KVb$R=n4 zvES$OqRV_LtbbnFs2e-~T>F$+Tee&KFz1vD>C`sQ)TI=mBR(H3_R%|oh4VtiF3Lw_ z7tdE0!H=H2f)&ytAwMlWbDnuG(ULf9m*DTI1h-oaT(SX8kWAje29U8iM_5m`S?wCh z|2)fTcQ|>_y8p(TEt&BeR`_UPS^SO_Aw+z!Pzmz)2I2q4*o0Z?4L!A|{tFwR-u=j9 zsk_AMkBW&!9LF;X`vOexf?OkPMS?qF1or}T8%dvO4jne0W%dkm317^C;}z8p2F%50 zC&$arDGBdTWteETu7-Ej;`Eo6}jy1~TUaAs~m zhhS2-ZEu)clw!Zg9(sfvs-2Us;-4ssADLua7E|t`zlU(bj*`I2HTml-oa)BD4e;6x z#Il6qrF;-Y&tW8D@woFayo)8iO4hl9<<`}vd|k|mufrz)`$@MDyYyXLUZ9H^p@Jxe zn3mtSIH_Iw3x1|2Uhj^WaR8u^ISw=>@4vIf@UM=kjX!9O{)a6V`2W#l{>NGNfA8Xd zH=IuY-n}iVHvby@n;Z4Nh6Epb#M;g4i74tF_sb-Rd>-;(kwu z!RK#BjQOW9?`I~}#+8PwCNmj9+V$-8Ece{>&Gqh|xAzMwe+X%;d4~ahM4=pFn5%J& z@T0^41a(ePmuQCKNZXc45sKg7Sq99%CmTnsy4$U_RC+C;tYjWEXHr!g4%MNwS8o=t zU5BBC4m*jkf0GUk%P;RA01A1p(jYj9Vw|c~O0{}Vr%@Vn#JfdxEAB5UcKs;NtiXs5`3}FZBK{*S)g3 z$55~%jX_?tZ2!@XL*pbtJ0W!BhNlhcAlYmd__dLYu$LT3VyZdB7?{G*%+mk){+zJ4 zs;d!SlV0vINdFQ8yIDmbS|~){ZQ+Xl-0nVjY{WBZH5Ok(qD#50@k&HaWJ=SGQjG>sw?0g%xYX zo)I%5ZHB10EwcdHota@yKcn98pHZ*azYhpLLnCWD!~gxero1VS zp@{gsIoVg3UI+zeB3s%p_gfSf;DeNK@ONMnGm*)fS&4SKAx4v=6GM980?4Bv)-VW8 z#%=F+UKG0m8qZe7ZTAh#?Cr)Tq8}KQ_&S>Q)0X>H>+#1=Ija73_V>pJg^y?j*~!oY z-dh3EgHGCh#cwnQaC#T22>X=76ohcssCz$4SzkX0OcV~A(0xas~l-q|+(dlYU+po{VjMHA~h+?A9sV>Gg8pemGtgwQ5AD<1!^m1fsM?$4U=Pdx_dA z1Vdd^{^<QaRq{WW`$q8N+3kYCzjK`3k>V=-aI z24Nj-l1^-9@jCMfs_jjagNd?f30jHf$A9_`|w#Lm3Kw0)GM{<}zxR z>)9>F0>Hl3fVi{#9s@Nu0wh9jAuXw^`{pc}oS@tT^KC?^x}q(lC%Kz#g8xDh&VExs zNwY#ntAS8{_V% z>+5d(Cat43U!n=EJ35}M^%!aT7r^byL#@M=>I%4i#Ns}GAERjzpA-XOl0L$U&V?$O zU5Et*b(n1e(Qj=l+Kt#miKG*{HUE^I6ZIRiZkqVvq{2)w$2r|dfN{q6-d5PiP=H>y zFfj3n#fJ%9Wti#CMh3gPv`;=Zu!_H}OdwcEN1rtFVw`_} z_Z7iZ!2v$7Z1VH$Qo_SQ#Tns=?5 z`x!jNy9?0?NhcNi)A88qo3M6Dd#sE$?1>im5Hw1V3NN-b%$fzwzRli)mN1NdKEb(pdIM^yv_VSLm-8J|0?3wwKx390yng>H+3*|GL-*W zhqW^PVcIsjKMvvlr>9Td{6EOHk^L&Om4yV2S>uv;W9x#II$Ugm-=BcL6@dv|(oORY zX7m_FEQ`+Ch_@gwICp#EKsW=&-ti&EPRU}DiodxpG8l}z?0>$@*Qfn^lwUA4vHp>T zn8Xuty_)qK^|cm#L>NdIiWn4-tCFP#ErT)SiO;BWj^5g|5=@2g>;78mCz@MVas?|7 zTw9y_YH6PE62ZarIw}?Se;E~U6>#}oDb;e5%H*HjJ*!+#%z=w@6J{Q%VSe+1aY$-A zYiu2F<=VJ^sE|Gv9({JrR4pe`8$PwHv2b13V1af%!1$s2UkY;kRS;<6g!xUC8O*#Q-fj;-J7t=$q+gn)jXnj( z1wxL)j~-PE{e9s9bfni~T8*~RgP&P!!_c?gcR8}vTUg>9en5>d&RK=wqPzDm#gp4$ zj01f?E#o{t{#5aQ|3r&h{ZwH5!#4lnpFjQM4u=2m&Px?_6-;NO@5vh4aaz$4;+Vfo zXzFr0t(35F%ut&_KV4xqqT+;eWs@}=fuc#Njz-9FE@W#<@0CnSrHbWCOXB6BNkoY5 zx5$>A@1ET6XYn+j+&CX^rNsROBZnuWN+;2(HE>lR0 zdt+vO8Q`bJK=B4C;yF_|RX7V=U2w9SiCA@8{v$N4F98y0ULq4>-vfwx=hNc^ke)jP z=JtUX3@51;5GL@pCPIo6e?R{P_1Z&Yh~!3;`{l=LI!TdT+GBjnhRsd0E4$?t(cF!z z4~#=v5NNe=^9uQHzBg*}*h}OJs4&Oz+O9l{@=ma&6>15fDnS3Lu zhNjlUH_tu4aG8~G#M(x%^W-&-9c^k#MVC8F+(@<=A-S%`Ub$W?Fc$Kt5+9$Idch*` z8DPZGrrDga&I@4J#R*`!JUMdw*O>xdJluM;2O(QyC6bm(|7=LXtOMpeK2{Oc%&@VGgIM}n=xPTsHZu*o|%=ydsHI*DGc2AD4b$rWMYr_F+cj(?lYu$Y(d0;`Gym zsVB+o4{0WaVAxWNLo&g-2maMO*qGgJH^Fz&7= z2fEolQG2QIcl}C3QYX&n7uJjBQw?>=S+N}$3TvDBB4GzLg zRLYKx^=)OTX4DgErJ$67t1~NTT)b{xDBJpm-PJp6oYIFy>k5yf4es3Dl0RBGlcl=6 zkeqZGj7n2lOVEiD7>~>izlNL*I0?~Dk3B&I=?k3@VF&JxNNflsY7~FfIS1h??ud;d z(DEysJz}!|k{hFP%wR_V1vv6eo}VD6bZprUiHm6Oc!Z({ZoD1T7?|r-)XyP$bG-Kk zs+K#Tcp+0iFn)Ojr~N=xynz_nO>QaMQGRLk!77)=oI))vu#!h&Wy>uG*Xlp#{1EDy z%3$r6jdxpHLNJIgSmO)!3NMHED&BdX_<))Ch(?8pE>b8Lyn%w;OM+3lR+y?QTQooRsb|E)Y+ibYPpR&p z6s+)b!X(VTwzS7+!HF5!N~m_e9HxfjR~m1(1NVhmD`i`y54ph*TuOHuB+7D#w|bn^rs6qM}j4>u88m-909 z8Qn378h$ehryt=81-d2(punML3ZG(*KwecJa-AGkfNPyvMS%^{9mNgCm4!IL&HC@J z^l77MMF&_St=`G-5)v585Jn?7Ln~EA!8Fe_82Ch>P0PpQ+VT)sB9MB@HR@Z3(I;CA zJo(00bBCDqE0P=Q-p@S%iEzyp(jhvEEnkvBeitFmh~)w7kJK)2IQLuSThcG;t;19m zA}y3r+ik(BUg}RFoeS0@+Aw!O=T#}{7vd=KmTSobahGQvS@-iPF`2(zEWZ|rcL;+h z*A_P95X#6hgKb=iO8R&>Lx(@?U7Hnbcz{}VWQ+Y_<#T}WigYMJ>43m!22#ZMp5gld zvjS`{o;AuM{G5Q_d%Q8HaIyEgX^dy2Nw)g^$op4#@1uRb@iKc^`0oDIN}!Mz`O)-4 zeusYO!vEkuT+-Cu{)g`VLl%DQ1^)|Es7&0Jo|i!!?smr5TtY%458>ez*n}wn6hK@k z`Jf#NB}A3*Xpcyjt>2`!1o+JMh!McM?KR%_f7^?f=04Td*%F0@2j|n!kd%~Ws5j%c1tuc1<14SI~GT{=5FRz6U0JD0S?LmuiOd&*a4Hl2GA3j*mk~0 zHG{zh;!{+DZUTEyhhE~-I~nx~s|gCSu*A?HC1m3($CYe+6H9wDyGls11or9(nytJ| zd*-n%2D@K`5fS*rJ)?+*sq?mMo6t0*6fGywY7RRNIp4Ub#|f4Kahsq^&@5tt_sEw0 z6$tBs!r=*u#H5mic33oSM;v_oggvkemK}+&k^{?7?z2fqgf*5IzCiS_fY*Gr3UPfh4gBdXY(XjrTV_9xzp6snGzFWJz6*U5Ae z>b#^$8`}Oa>Yx%)Z5Ua^{d@1j`9<3&2(qX3VKiS|pK-r78?u0jI73d-73h_vE*v9^nb#_S=Y|+zY*z1#s8FFs5YJ2SHfgyTzIL#sp<+tP{L67dQd6i78rY* zPo1dBFRd8bfj;rLUm!egc@bm@LV0>{3_0s5RelFi_9kbtHD7z!KV_t9cYA;Qp^bbc zltWd_-A&ujR6b=W(!+E`0+JwY$>sB{$|=DQjq@`FVnLG&nzyoVm#wvk&sDJ%kUz$< zsz`N9uTKBzKyxY92j4VNeFI0ST2*<$kTnW%H&05Zz(!w3IP3>SMCedaI4A zV!|4#j{auL*KY|)(UQMQZG@D-G_i}_&nIGbPs1fosoM8gw&|v0gvu#GWiJny6dkAA z-tutWs3nWft)s%3*w5>H2Uz2q{mj;TB{`%`((Z0bgJ@|&bigU0=wieD!l+jHeA2opi z+<@NBOcX&dBF*y`WU)wDjBvt|L{|-1lJPd|sI&$C8(Rp_U|c3sZXHuWY9QX6;iwQ@ zLl)3S<^&wxggq*BjIn5v)~&}bg&vOc?VbThy}Qj`JF9KRFi;(X#(;=Vy)XB6dBV3J zDevR#SQo(;_9_)=xm+BwUe=4x19DusZ;98PG=+T`ysxWBjg|D)oYj_G%rpHZl7LV) zX$v2yquc{&c9dXA4Uk6IXmP8L=$*(MyP&AihZ^D6zu3_R{e=R?eo&(G zgA&1i|9A5rl>F<&q)_1>d>FMGiksGIAa&&UH3jzB36t8@&K8KuOPGl~Sdzxq8MLok zG>?S8p?u(Vy!;k|@2}?>b17=?6)Ue>Yv6hw&-f2<^6QYo2k0O#M4vuP>vh?m3~FAs zWF|jlFeAtn3PM((0JAqP$ndl)Z#OhZ5y~7=^E}9~1p_iy!7Z70a`oMBSE#o}pjLJh zVTz*5IIgH$C%LtC9E*RfOV079G@4(p_z1lzvA&$?%4XRKRqv;AP-^Pnu?;u+((h8i zL2LgIFjx6Cw&tN3x_U7nKUtE$c!a$9$#6D#qZGn;&uoa&U&%^Lp(&%yiJeB8xx|}Y z`tgF8XP6d)@q^wa%SeIAAnL0Rk7uuKv@%S~4y(V+fD5CQP@ZZivy)%ess1v}K?`t@ zQuF)fi}JY6u72#6vftxICFm+nwzg$GCg1zMT?(U0_l)Pc5!=B4LxEJS4ns<{gO;!< zXgw`8Hc(F_hbG98bMbG9=a+QL9r8@r^6nI{s-;H15v2MGagO#T9zUH9Ae$D7YdLjA z+b+6rUT1u5x61&npD`pu?-5155E}FMJ^B~@Z|iSJ|IA;1n~6ymKz||ax)GgDo`@H! z=P1HkG53^qWlx#xF?6NhQERNoVoC3Pkt;yj{nM9isXV40D1&?jp+)C!d0N7Z~W~jmsBwN~D`fatRBJZO#*%k>!yjFS^0uKVbnUJd2Ryq$#3wPIxJfZVqJ{k&L&9 zXGCBQb4AEn#6de{voh66ZgSnUtK&f&3VPU`{pLb@%fxrO3nm!q)B}6PdXBGvSNwRb znYu@N!ldSa(*GSjg59@YnmN^50&QLU~Q;g};bg&FW1uN-D6+(tiSj13|*jaU7szS?JO%dg{la; zsYTbJ>S51)l`=Ja293O0qU*grE{>~Vl~KEju8(CD)=RK6c8wXv=Ry{0eQY>gXHbMs zf(9?Q^CXoZo16h3k5t4ol0WgU@(59J#$rXL#!T$oiR2;)m5l~P=ou9rBG zKW3L*?Z8_lpgc$u*MB}N{M3p2H4S>dtnu8Y?ig969?)uZXiMBkgy{rwyvHX{IwQ*1 zAaq*bEdCiNur{67aksM~O|G6rDQ9Zva~!a|*~U!cX7%1NuGu&KR{sIq?_r_$D%$FK zxv_K6f~%Io%g_V7`)TPMKhqWVq~k!XKec!HEiArL`92$v=|=Fy{>{a`u^4b%_X}@F zaX=)3VSRhobHA_OLU51xa|m;}5)1(E>KAu5Af;kUL_1Q|j#ePnvNgw%f9VT`kTto~ zH}bUvD8g--TZr)D%6`~)z-4bH@U}GFb+C$o1;du}!_&pT=wTNZRcmcOcPPeBVAB6U zApYkL{b%<4&!DbQ;Zh1g7M80S$3itpF5HI{9ABip!2*Jmd?dIe6pq(l?`GSuohd_}1NBcI-LaLWPNMI*u862C=;tK_$ z(n&p`Ly#LKfE1kWXOo8=oF9Zma{O61Y#!*hdweURwIrF`@}}l=L)N;UYbO*a0={5B zQUPPZEY(0o5Osk`nMW4tB5m+6q$f&l_QhIa+@Wd8uwM`_ByCMc5C*DD%?Pb~C@-qq zcUh(7rHYZwlq0;NNurHgAibV_8IBFj&GvdPGrx4aFyXuJ79qf40_xr5Z*&bu?vUHi zrL{iT&VA80Zh;VY{H%tC6_8BZ({o_1Zv)FXq{4b}9w7xB9s!AIEI+J~1?*I0z!gqC z3xG=tIMJp6tvi@N)02M3zh-%m@oA)pc$rU1H2dNhDf8U~Nl`etmlVKWe5;&7d?}X) z#txXgpFv;o;ZgP|?+G}GT#aCqPZCeLfh~{RR&(0C1`nBj>JD@+Yd*Zipb_W7Gf&dR z5V2ZWykWs2WOT2WZg=R5kzfX%oX!y=y@3yCsa3&v#Q~(KRS0=IQG@~}1gL_Hi9MPT zOb$ZvS{D{a8pi$b?0yjmst@Cz0w#;kwov4k0bZp8{{js0aEg`EA7HHgs5Ad#3jY5h z$|y+wcqmZ4jM^{z+5*F5kf?I-8xU8MX!ONG3S{RC{6wKbw}R+RQPww&oWsAMXvhap zt+d>3e}@taRsYzaJdD+4Db3PcR$O_GT)VSUS82Aly#Lhr7-D^DHL6>UFAa!(Z`tDH2S}%#z)&5j#_v zI%kw=H*yBO2=zB(wjZ=7X^wI{0z0=}w?GQ@HU*|v+fE|{v@1JogpFc!`~(7k&3Q|dsgmZW#r!!e8PcYLjUy34;4uRDf z9#U%h>|eU(4V1H2NwYq^1oLj0j2<77JiF#IyodH-sB`399Jg_m`T>J$i9NBqF_T2| zyC&(TTyrJmb{i;KT(J-dQ+S^>oT@Y3lhjgdc2vlbcOEcq*0q?A*6wQ_9vQ>{0LuDb zZRZ6M1wCSOOxa5#T1c;C9jdqIy%R@%1LB=aqoVR=;61$~LOOqq4|2q|NfP$om`cza zxN$MGnK9`qf0*4Mo_0+=CIO(it+Jy|&3OL}#D@u}0H~9Qi!g9G0v+R!Lxh||kCi%P z(<{KR{57SQLKrXLIm6Z6l& zc$4!0Kzl;r(d}r&AQ6n@8xKsH{QdVC#Q%mnNLtVTh4tKLwY8B;`=gfQktp{QX3*lp z`jUi_(Lx+oeZBQoN2=!c z*Zn<;PjN}Bi2kG?u(|4nb8Qp|G&Vaa0zF69U4C+aLaW{18t48hLP};2qUR{TriE(( z_nufef{Tz|-WBOp)YCQ zAo-a9Tr1n4nZc&V?(4X#(kb*jw}?4Yd6IXU`Uo~-tv&3WlZt7X=AE&j>pXna8_WF7 zu%l%hY6M+wzY%r-KGIFb{7Rh~U65B(_(#e9GL)8hnJqlywnCmU+XCwELaE~6}7dR^0< zmG6o(Pe~FJK>Sp-LmmQ_Y{Ny|<%<-BV3k!?K4k7SP4Ui}8v#G&m)pT5%^uHxV*AOf5Z3mFX_%v@} zNJoU0h@y`^L0CQPfmGf{+kDXi6rb#B zHBK+?u?~L}H9l@Q&SWpRuHhg?M142jRAWZ!52aHNiFbvJ8aIyf!pst`fjGf5-6-f= zwb!bz9W=``d@FkoH4BPMZw#@XZv2wK9l1@uAviWs!4QCw$(cAyCaF|bC^_yq$P%7Z zu{nCX$L?(D3Z0;9JzjM5)QOA}SWlpp#I+9B9jRNo7%=6RC*+7oc@0!e*%D|r3Xd&G zl(~xANHEg(s8pe8%^PLPo!Pq5z$A2(dTpf|bb^>)2{CN|a^v@|NwKqqt4y zZJw|xD>_7omTcgs+u=xRHk>B!XurguZl!#dFd1?Y8D;e#LZ6?H0EVS0ayB!QtN-g$ zcH%6hKcDnOkn3A`eE6n7uz(m=Q__Lq7zgQdsbNhgsPy3#m~(CooW9}SsSp8C3pFuJO|^k466PtsDJwZU4jVD^=Zf6c$sz zJx3=tMkj&d{`&C7jN}vI;f;uc?!x`X7yFG4w_mUx-5YG#Gg~Rqd!M6RXb^Pvi z%t2y}>Hezt%l@$N_n%u|v#*jgp3)OuAYCVJJ)n-Lh+21Y{5( z{EQ?{{yV5!#4u$K;;=zlSwb&nd8J2pr6J!ak^wTk~#7Pug_Ji~W zzIeweDy5|82Dy0Q5*14Ejdd$Dj$?r03lnnPl=5km%95RA6a~DGO6YZEuqdOgUaFQO zu4U~)q1@XvD5O}+Z-ug-R`dp$p%jSwk9xHvD07!%0Tc#7cqp%hs;f4&p-QVcZpkl( z`ElaX+Gb+m8b%|Bzs)6CF9b07oG6b5{^&0|4*JL1*mI&oIx`Bew_lWCMGHW+^3k^T zMzNXq(UD+64Ee8TSm5)lC^r`p9Ug|pAbz()b%^tO2IYYLF!PBtzZWsd% zvISKmColu+(}g)1pXXz_g*7c$hjGX{Ga7|Zq2>!uK?&*K9$hJ&Et&?ekLm>0lfgUI z4MCYovgLTSV>!|vG=YIL0FMldJtyfX3?Oyt8JihgBD<$+&SSv@nW0}+4f^>V=?Jex zISZFs+aFnEzB3pEbC_uWhcEv`H8VLSZ#J!#o;EbI?WSGIwwI5GE;R)DF@be11NTRj zkL(pD$XEpP#a>4CVoAC8AxU(M|H*%J8Pc*TD%d;?W4CO2VlbT3e26X=rIpJMW)||t zBtD;=S4a_foJ;IY*+jQH0n*l_#f+dqI!IR5z`tP>Si>@8Uo<S{B0)7%2v-7I!k$kBpHTmCx3?f$ z-V45|wQlS}4y_x{$ax0I*8%XXm3rf9hzemc%s^*5MWkUflo)UxE7I_{PCY`gk8D7? zq}n;5q%8X6nvMkAp|ztEy>0Vq?p3_-m<;NH90_JLIdb`iwJGs})O^2~OaVug9$s;( z1TZ#2rV}R?B2&11e18F2sxI5*ZBPkV_iN@8bnk)$Oa^XTk>TskAA@lF)Y$Wlk=8bD z^~8Br&7r7Oww1+Qove3QT|**)gcG2hqNcwNmx zdKav4mfpGzC$czs#!CmON)5DFpNkY2Zp|nDF;s7?)6KX+izo--brmr3100TkLCV3NKFgNP zzRDHL-TM{8UGWvFl$e9gDvqs1tm7e8r(%k}m`Y@=_?SSB!g#1F`AJPqV30|!=_t#h z(Fz>96BCh@xDW?bmtWDKMo`x_sQAIHQw8-0=%M6^dS$u~RhUPwsr4pG9c@snMx#!v zz4g;^nRb;#+41L~7pu1BqmOog{Kai+aTtfhd#kjHA~ZLN2kB_bi;KzHjR#|?NgMbq zDtE4{hNCD4;Yl8%E#gLcPNNlK;#P_4h`pCd8+gw2kPiuIy;x?#P+wJDc1lF@JeRB@ z$Q|W*vmy&|?Fno9LHPW%3srylO;$JUqKUMV+^Jr}>;^sS*5lp}0mQKrIH+7jfcj1_ zg+s$)`O(~+Z5M1?oCRX%$?t%xb;lIl73z~;%t!lwX8%D0z6e`q4aN9(@%@&dO|W@V z;++@g`9#rU`e;?9(L$G*XN(8Bx}*DJ_pXYD$X;RIbq8Rr%D=?B$lobn(>RSrmZ>`M z-l<&a!zIsh8VZC13ys|@+*k?NH}m`AtVbM^IEkd?ryM$Cw+$2q#>N(Yi)YDlurNR8 z>WtKfeX;c>G{i;QZ0iQAs5v{=VT)>lsdThblcv*gG3QgFQq=PcL_cL3UQ$N(Nxf4R z4mK|YaaoT7B+@rRIk94fCa+#z8pbv>GA{?k6IfD9Qd$Y`8?O7`P8u?l8Bd@O1+~5F zk3b}KkS^EVpdSt0anCSL5RrJwt8hsKk+@l)dZiqBrNB~tHz-%_@?V2tbD~Rua0hn; zWoW$_b;r;ONq=)Qf5hY79~#b-t;BQ{x$wsnqi}_51Z!v z?L4$6bsRH{)NG@|>9RUTPPU;ONhxDMcV4ew6>^FOq?dPAiRxB-ce;+K97R*jDvO87 z%8ORzfSUXc=Fjj9(@u|Z<>=g^{8`_qMa2JjSc)TIdA9;7Ovs|WIF^2?5?@bHmEE9n z?$-A4c@Mu-|KO#O;O7Z`a9q zxJ`0HDXm>7us3bPC>`CLNegu8cx_I)SX5V?5VP5TcLnIIvESG{2TtKQ!ND(1UekCl zc7Z~|Rf=E8iPbjA*?%a-$`REL@!^e6s)e9S6@+6`78Q&|uy3@IdM-hfL5b}12!>@7 zfi4+{dXzwG`c-9RA($`Q=dT2GyitLcY8XS@vZwkO3Ci+XqErPHx&*hRQ>k!PAe-D( zKu_wUU(Mob>8;nnjzNB<#*tzzfAQ<1dwkKY{0Grhe`2(zv-PHPL9cVv!zUYJW6qGB=2E|tUuu!j*P^h z6A5wz`(>$mvRL93>J%R=#xIxH;;J2358v*)8^Nzz=BoGRGwaZ{3P8dA#muN~;kYDc z>n7*>Wq6krKp{owp7p!m9-g#sJ3KjP8~sZMC@ntYOMBxNs?=;(gUT<86<6XlZGIJq zmjh$mh%uR~bHRQ7BgV^SsjIB;v!HL`s&hF=eEGq3m?O6obVrt*UTHzU@Z4X z-?+ybh4+k#yoVF~sH@?!)5R-q4Q|Rswd5kTiVN*bX#f!fWUUvZ%G_8Wh_-8~Krz1T{UZn5L6|icUfS5@Q;jk& zVuJ-%WbUU5U_BeB_uF?JDo7x^y#3+W2V|U%!@mnHH_HruYy(upytxuSII3PphBQALx?9`yvjWq z!{rDyhWNr%9n&I}DeE;wT&`j5^IrP1xa2A;y)KY>>7rzO`p2Zq`2~9mCr27&C9Y}$ zfx-Fm65aMd-EO3PxIP63dL05*oaG(80iFDGhV@zm4jY1XbsMVt3-+Lk$CYS|8+hS& z8-%Yo2Jc~sPn4sx_K6vo)bL^3@`#>GdT8enLM_X2n`ng{EjEy6QHHDJ@!K4W-u}5j z;R82L;^tjjS9s~0wa*aDf%rR1PNM34(^t5xCC6U85Qv z#9;JkXR1$G`yyCjQMyIG)@UwUJ-!4f);oc9t_(w1yln2mwLz7>DA6+c{VHy#uD;PW zN?W=wE0W_bC`8(N-?(lFJxtjI;7k!>)4VR^AiV>FUDtB2%X2l;BD&j^t*Qr5y0^;) zw?b0Lo~#FTBRnG3aNY;OfGPz$bxA(;DSs7~`8HJMf(s=V$pp@Z>o_eid+dOnJS&Ua za40~9C)`k?Zi>!KS8xnaf9n^g-+oHVESv4eYS(du>_~|A515P|J4yDM=;2 zM0UyQN$}xOR(jHhN`2J1+j$tsogdDId=a1G34kCCB(G4k&=$@;>O>I|B>>^{_48Sc zF7goM;qdlV<~?UOte=}I&Ji_tE;=J>U=Zsh&qu-Rdjs0a+UHRgr^ak6plCe6KMeF@ zJU>)>K~p3`ao6e%LWVNsOi6dIjRmGE6I-(kifp$A3{Sw{=m9-@#~)7C{Vyvh&i?kDsRp06ZX^m-c+W=jeJ^p~r` z&+tq(N2?f3FuG>)h|bl(t=@I?$kxS)Nd|=ilsIL(qm|b|;aqq@BJM+w07*Q$e{p1b zO-~@UruWqZ<2gtf-?x_M^b)WpXI+Vm9hQZ_$sO<6#&`h%{5IL4!UqK9F4uw1q`lGK z{0=2%_apif(a-9CV}ppmK!6k0&h0_%`)R_3$Lf)y<^B~YGbDr6N0;I?p&eL8ihQ+5`uJtvS zwQtSfbOCxj}B3QIBrNu;DxC)>e6{U)~!hCzoqNp zny3{~n|&&G;_;E;K01dODI8 zgce24dlcM~M_7Q@}Ut2iC8q15dzD=iGf1Qb}_RWK_mU~xGb!Gi?!VX_-6|Lq=cFf7%4eVe=NU9K=Wtel9tQbDhyk7@)G zaj0%HnuKM}X@kYq@wq8P8UR1P)|Y09o!s#I`tXB|@NbghgAV!lkM0-Gs6jjMIJD5~ zLTaM>2S^zW_=`bgY{)EZmpg5NLtngzEc@%fOLn^h?{04}l=FyNQF^+-l}ln;N$hmK zs2B#P%)WyHu$muQ{niPwIQuM9iJKo*_bCE-xZ`Z`Ay@{x264);+4~-3-OIP`T-_`# zcPeW@wg{)zN6*M}nuJ;(iPbyb|6*;C%?G9x{IRt_{!DECkKr)?_lU;ef7!wRXIhh~ z{OXLMjPxZGE}TT-R6%H#QB;~Xm}EFe9!XYu$?iDUVr#}hM9pkPMw>)@R}d$J6`8?0 zlQf6iR@+cvy2>IC8e=EIH=_Fr1?>&keJd>^B{lK96=5)r-aH_DJkfsL)$Vn@#gXs5 z^)|2l3$yQ#bdR)*R1ofOEmCKVLP9=hd%Cg0imbqfWFZuEnWf4A+bwIgp6Fm8DZ5NW z9#*z_|FNv%tp!F_|2^DKvo?fmnI~PCrHkyKxU54iYVWw-r`#WH1%;I6#AaySpFu+JAajI9B6z9S6suF{--a*iU!GEB`hCyV+7663v!t`g(2DAf^( zvqL8QNtR_6sWrH?nM7C`d^aC+_^@#|yt$va@g@GW)5eal`&80|=ud zy3H!oR{ftWnPfWzqfu6(PngIVY4=rTa-mUM)x;s0BB)^ecXT%Ht3tf}4*m0dr!KVu zHuSYNA8)lLcAv_i3|cY6Gmlf87vpW zgQK60L2h^GY9g%N=dM-xTG!K_Ac~xyX35Q)Ff>57LNZBXOgcjz2f@}X4z`BsMOa+#jN$U=Mv3JwNnzIQSVcM;*Z3^E zA{w3pwPu#}T&w5q>C*~S!>Ck;QfkE4_@~-}UTIWF({*R?NVbKF#Tt%?4oqa2m1%() zy5ShK6#7M)xe0fFu-=Hz<HZzOA9QOVm*w#3~(}3Db$((Bg$sXXoT3D=1ov zkfK!s{bCbgA!eie60>QMBl$du2R;Ll3Orz#P0szlxIga=FiAe;RxOO3j-ZZT+Q5*? z6Q|eE7B>era5Jggs7a`%P6Eqn0q!c6Z}Qx?#9q-qP&^E*n=zQ71Rd7O)>QQ;5D{>< z2$yN_=V^VeVH*_*rA`uoo|=OY-_oF8)MjR)Bm6AOLGqg_X~2FldHi{{#Wi`MrnVzD zalyDY`H#%&obRVPCEA+Q3Z{==JPNl2U5QKkReQteUVho+E$bNh{-J=04tckZ#4b={ z#YfY19!wIu2|?Mr#~!MdwAhG$=D?u3d+3Y#ql3UC%v@ma(Y->Q6+guK5nSZ@t8GPl zx0v*OK4X_58bPD7r_r&0b8Ke7bAga^g~lBc+6|!@rJbWB4|#ay?>4(A_g~*E1n;i@ zK}pYZg7p5CMF#s2%bg+NMygbkP)>)A8rmWDUoh6^L%h% zUUA?NX=0>Bf2xpSkG+4hsathn7-sQHVo1_lFx>~p=JvevkF4kt|1(jzakgQep^wom zfv;MAa8fkl6)X+?yXVr&KOyuO2y@d*%*(WiWs2?0ULdr`zIB!l;Q2S1<20 z7k5(g7f7pd_44zx-869ZHB4^e`7ds-q;y|P;N;>sldO2o=P!Jawe8~XL`#|I-*kidTo?f;>AJ5z^yPW zL_Yy?tCFf_94%n=(yi!hm6D8JwG0Jd^AsX>tTdbR>88;CQdLJ z+Iljw44H!snRV~hZ+`*L@|C{R2I#7>_C4}O(DEM*Z}R&T2-zmMU=mc?Isr*%;l2Z6E@GdQXQ zE6yFGUdVB+48dw^#eF9P@tRto9xXw7caarv>W81sy`xkBCuxLSS zJYB2+XzL$#8wSySDztc86VU-1jzEqUjNycoV#A3LHku%J`m6DjMA&sBA%70|xj?F> z$%deE3^iWo4K}dQJT1D^^_tdz*`(?FuPq%TL5j8}E2Sgk6A=q77Ds1ZK30w{YP>p& z#8Vq#UY6HzAXjm1xJI4Cl-el^%?p2>fy%Q1LhYK1u%WXGg+sMSOM7{D<9fHu zb+yr%#^ebn7uVIY#S~TK9&<jqK}aJc*IBTk3GesKj0%hEbwuH<+{l)@|rc5 z-GAQ-{>shxYk_GNTO?bgUxJQ-v*(hd_CtaB7b_}5`75XJCbf7RdWO2IB<%VdjUhYJ z7abavE%-q)IMZ(_rXmIk8F0$b2D^fJ^0L!SFQ5mNFGF1!vnRa4I-tx|iXn0K<@piu zn!I_Zc>>#8+J`5P%s$me=Di=Bw0FgqGs=|<>MNzw1bHV!z{tO=ts#3LXvR1i7b-bB z(+XTuNJdAmk#H8ahCAUo5Qv$Z{fbN`t@EL+^l`ZQC3gjy8wnWDjeoZ~-X)RmQva6+ zAGHTbjm(R?DsQ^~dbshIIZMyjaTi`&a1+4*v%>4I+w4}F5KMetKAu0j2ezypAqt?~ zIT!PzHOjTgtiStX=)^XLORSQ-T8qwJbKZV^5`a2_Gx?9e%J=f;XO4t{e|#d~(b1GJ z^$Gx@Zl~deLFp61-Us0Gwc!6HhMq<4J6Dn~itURCUOqntcF|)BJI97<8wc2{_enZy zpQYA?u{$78y*U+Vo3?EV&0iyA3X^e@^)cYW-}n9(1BqMq&0Wxs1(oS1R!Zdmh#os@ zGedoc|34|qg>mCjeSZ;yrfpDU|J?f7%CZ25%mj+lgz{;?5%t#KjMYM#a!k_dxKL=O zw%h=CknWQy=-0?1w6l62Uw>z^%}<=K-$VSu?AJn;lNsw#0&Zfci4WRjOh7A;3M6@8 z^LHs+(~mJ31E3#i4h&vKXpTNhdd9K~voy6W9!>;Z%1xc&r!$%{6E{rXI9`I4OqQNy zxJG*RRQSJ2I}>;)w>OSYhR9M~LZos{lo*6aQd!12G`6~;m}DQuPLfa|WlLRKT+1|B zveXroREliLTFIIgd*oJ1uD}18D_+jkpnH6Ltk3UzmiN5pJ?FgVd8qGL{!Dwzg4I zc39+X9C0Lx{^I$>^PQTBw{Rf3>3_1Om{>t(y9z0b^~)7bDnHXYu{`Eble#U_&d!&& zqO0muWxsKCv7awPsWYwfe3b6hW)i9BW@9*n&ud8*nVdYs9=}KKc5lSZ*Y`aF(3%ap zE0P%VUey^Lu(i4%-Ej2%ie^l4si4mG?ef)m+S?0RB6Dg+JSu{nl}^7YYktIO@2mXg zk6v{~eslFzn0gh)_}|ncga~)ueQfGhocpp+;sA$J2xw~&(AF9YwKW`wbJkP_az%>tbe^WB+J|Mg2}58P`%3hV|#z$|=ikYS{X?2i_aoWVRqrw4GpRmSYS!x-AdZqF1dN@&?yW(6tB{}(slgRUw^dojogkv5-xylMbrrR#(P?LBG6U_1d zQ-8r#_esbnGGsqz-4h|7i~gBpB{xT3sAEf?O&#b5@0H&NPIZ((W9#CKl(AZR>XME` zPb()$5P(&J=uEVS-MZpoOfkqk;1$&rj&6sb^2G1b7ka?Ij}Axx}kXn%#&Ka~=( zBEvbvGPh3#IS#_E#a-6As2n2Z8TwkqN*zO|#2W&)1eLqCc(ck-Ndj;4+eDMHIV!@E z2`}z$+Q+u8`;uvWxbY`D(P8UE-9Rw>pa4WEPe**>A*Ffc}-k zi2sj41}83Yj_aGWadB=UoS))DMxUQ;iFq7o#;?R<_pkho;(Z-2L8j8P^u^D%f+dPG;UpB}sTa&=$IoCtP3saye==&j8<*KzwMwDHF+b<+pKzqR{Y_P<(F0mwn zrcl;zL6KVauEe4gHDhPT>Z@l>wLeSVa>1q*r+G8fesLU+(e^7VMd_Za%hk|*$~GF3 zn(%p#^~OgrCASlWg73E2-_vMibv(SI?cLZI?rTqZtAZ%clOC0It!$JlW0yQ1n#S!g z*z@YiP5%vnB#(n^Cz#oLcZFs+q^eM3S-;B$08#&rD;RZ<<^bHMtZmD^iqw zuBB65e^pB8LmvG%aninJoT`EGDyKd=Wa&3AYvQlr4>f1xEy1lR(5T+zoBBF2uU+0g zDv*2a$^5ln%`9J`F_)uF_lEA&znh=2`?0e2I!uhX68b>eF0xOMaUf^1X~ue9sF|S;^NedDo+GnDO%C+Gy1zg=|O+5EmS8KfwBxOGp^YhWZl9LB+ zoWXCn6}9=cTl!D|ka`B=OG1C=u5GOp{kS!4e_KL!?fWQ3@Ge#H@5XwH z8|@}}^H&;Lh*`Eq-rHN*GBln$7*!&cCq~X4tGQ10-EhUmc2~V$442}#p4}EhN{}hO zt)h1`@j%<93zx6DSiUeHVsA)enh?3KU(twm7ct2hzoFi8Fhz4PBbR4oFYZ&Q$;dT> z!C3D0%&p~^eRAO~HLXDdSN+63B{Q}9X>L4NT6^*ZUtz>@ANBO)j_s3mRYP4t;v;y1 z1J$k76io@2(v=)lQ}ui_yf*ydMmBj?=0@)9wY8RMTQft)j}b1B_xu07p-@NTt1O1- zrP&glb2U2-`-Q`(;a+19I#@FcwNEcG3AfmuF+c=pxVoPID8#uB=m8}g~n(O(fV>{k-yrT z%?ghWQ)IKh$vXwJZ@YAD40G=ap`+1KK4p)Br_1Woavo@T^m<>PC&B#hU!|J&ey|k_ z4nD3pDDgS3(P11-Y$uQNhZVz5N6F>F!h6BZllEk!_MdK|&aPx|cXhY3a?=stT8Y=e zON`*J*XWAt)HGrxwZ*q+Vqa@ZR!L$}q20V!284MwiP%v31Gsxj)?B>8!)?>u^OApn zubibAoVP(51dG%rOn3B)1%o>rsY(~gcHxBV%zHNcGJAG5LXzusqp zf6xIB1mL$bi4w3Gd_OZ<=ql@JspAZdBy`p3fx$rYJ<-5uph=7HP0s?jFr8%~{M}+| zNTO>9R$pfs>diHr8rccBgeCIxUk5pYDmyHW0xgInO29$zSUV$u*HXpl8RB4To$Jl) z{=g^)d?NLZLQw)fbI!8X+h+vqVdLNM)J_c802p356&!dPP6 zCE7UwrwB-(Cm67|{rYWDP!Y8AfYQ_I;43A7XB{1Ynw2%tgXFFTJT;NX#G{D6V^}|d zVDJD7^jm?x;T-)4a6Qv{?DzgRb=^((gMaJ8lLIg#^ggES;cg28O4wNB&wi4wpM0>1vR)_@;4cOr@Ob#+|3e&Q7EJv(^^|?+hTO*&u!_h2Ss`y zx5A)}f$&VC1c<8AQN@#OY^LLn!S!0&Q*9~*T1_5YgpxCYw2a=t(UH`pO*9TnO)F@Z z{`~n3`;;u525tv@p!e>cBQ9@1N1Q-(w^ep?vvNE_t6@CZl1Ngs1HH`dhzAnP1TKgR z&x+=ipcT78VZ`UK6Yo4@10Zu1dFQ^1lLKX#%I7Y+9FjbP)?{2X?wBENh6hH0t!iov~!_g0%`C9z|%z*OpA9f0PuiVfdgO zf~Mpy6+QnL1HT-G5DZEdApC1jdVT`D&y5iJDway1HzLD3f(U2xlZ7~o-yeiq2;Q4Q zs9aAMpu!K)v!10Ec)Wr4NDwHhZq{nR)NJ^N3n_D#JihOkz~zHi5)l;c*?&PH>xu*& VCNKd3JGtOvEm(5t0lFyE{{i--k}m)N literal 0 HcmV?d00001 diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..7174b43a --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..e69de29b diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..0ad25db4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README.md b/README.md new file mode 100644 index 00000000..27acb989 --- /dev/null +++ b/README.md @@ -0,0 +1,136 @@ +

+ +logo + +[![](https://img.shields.io/badge/JDK-11+-orange)](https://www.oracle.com/au/java/technologies/javase/jdk11-archive-downloads.html) +[![](https://img.shields.io/badge/MySQL-8.0%2B-brightgreen)](https://www.mysql.com/downloads/) +[![](https://img.shields.io/badge/License-AGPL%203.0-orange)](https://github.com/topaim/eiam/blob/master/LICENSE) +[![](https://img.shields.io/badge/Maven-3.5.0+-brightgreen.svg)](https://maven.apache.org) + +[官方网站](https://eiam.topiam.cn) | [需求收集](https://github.com/topiam/eiam/issues/new) | [问题反馈](https://github.com/topiam/eiam/issues/new) + +
+ +-------------------------- + +
⭐️ 如果你喜欢 TopIAM,请给它一个 Star,您的支持将是我们前行的动力,项目正在积极开发,欢迎共建 👏🏻。
+ +-------------------------- + +## 项目介绍 + +**TopIAM** 数字身份管控平台,简称:EIAM(Employee Identity and Access Management), +用于管理企业内员工账号、权限、身份认证、应用访问,帮助整合部署在本地或云端的内部办公系统、业务系统及三方 SaaS +系统的所有身份,实现一个账号打通所有应用的服务。 + +## 产品价值 + +传统企业 IT +大多采用烟囱式建设方式,各系统独立建设账号体系、权限体系,当企业团队人数达到数十人至数百人时,由于系统数量多,在账户、密码、权限管理上会出现瓶颈。员工入职、离职,转岗都需要进行账号权限分配等此类管理,操作低效、功能重复、价值低,员工需要记录多套系统密码,容易出现安全问题,导致数据外泄。用户身份认证安全存疑,敏感系统缺乏严格的身份认证机制。 + +`EIAM` 提供一套集中式账号、权限、认证、应用、审计等,帮助企业打通身份数据孤岛,实现用户全生命周期,实现一个账号打通所有应用的服务,强化企业安全体系,提升组织管理效率,助力企业数字化转型升级。 + +## 核心特性 + ++ 提供统一组织信息管理,多维度建立对应关系,实现在一个平台对企业人员、组织架构、应用信息的高效统一管理。 ++ 支持钉钉、飞书、企业微信等身份源集成能力,实现系统和企业OA平台数据联动,以用户为管理基点,结合入职、离职、调岗、兼职等人事事件,关联其相关应用权限变化而变化,保证应用访问权限的安全控制。 ++ 支持多因素认证,行为验证码、社交认证,融合认证等机制,保证用户认证安全可靠。 ++ 支持微信、微博、QQ等社交认证集成,使企业具有快速纳入互联网化认证能力。 ++ 支持 `SAML2`,`OAuth2`,`OIDC`,`CAS`,表单代填等认证协议及机制,实现单点登录功能,预配置大量 SaaS 应用及传统应用模板,开箱即用。 ++ 完善的安全审计,详尽记录每一次用户行为,使每一步操作有据可循,实时记录企业信息安全状况,精准识别企业异常访问和潜在威胁的源头。 ++ 提供标准`REST`和`SCIM2.0`接口轻松完成机构用户同步,实现企业对于账号生命周期的精细化管理。 ++ 开源、安全、自主可控。 + +## 页面展示 + +> 仅展示部分UI页面 + ++ 管理端 + +![](https://user-images.githubusercontent.com/30397655/205442813-c5664670-aa12-4415-aa66-dadcddd04109.jpg) + +![](https://user-images.githubusercontent.com/30397655/206887727-2e6449a9-b75a-46f8-9fab-f215e2d26a38.png) + +![](https://user-images.githubusercontent.com/30397655/206887777-d1e6d138-8ab8-4d9b-87f6-08ea497a374e.png) + +![](https://user-images.githubusercontent.com/30397655/206887895-8c204839-9a74-4167-87f3-a131a528d444.png) + +-------------------------- + ++ 门户端 + +![](https://user-images.githubusercontent.com/30397655/206888201-93b1b261-40a6-4e83-b04c-8589da5f2f8a.jpg) + +## 在线演示 + ++ 管理端:https://eiam-console.topiam.cn ++ 用户端:https://eiam-portal.topiam.cn + +> 注意:演示环境已屏蔽敏感权限和相关操作 + +## 开源说明 + +一方面希望通过开源加强项目产品化程度;另一方面希望在社区中吸收更多的实践场景进而继续完善产品,也欢迎大家参与到项目中来。 + +为保证社区繁荣和项目自由健康发展,在开源许可证方面,`TopIAM` 选择采用 `AGPL-3.0` 开源协议,`AGPL-3.0` 是 OSI +批准的许可证,符合自由和开源软件的所有标准,**开源永远是我们的初心与核心,我们将始终不渝的坚持去做这件事,我们相信在社区的推动下,这件事我们一定会做的更好**。 + +或许很多开发者对此协议抱有一些疑问,开源社区目前也有很多采用 `AGPL-3.0` 协议的开源软件,例如 `MongoDB`、`Grafana`、`Loki` +等,维基百科还专门有一份列表列出了哪些[开源项目](https://en.wikipedia.org/wiki/Category:Software_using_the_GNU_AGPL_license) +采用了 `AGPL-3.0` 开源协议。 + +`AGPL-3.0` 协议有一个非常关键的点,即对修改上游开源项目代码后的二次分发版本必须也要开源,协议限制的是部分企业想 `Folk` +开源项目代码后进行闭源商业分发,跟上游开源项目的维护团队进行直接的商业竞争,如果仅仅只是企业内部自己使用而不进行任何层面修改,用户大可不必担心 `AGPL-3.0` +协议带来的限制, 这些条件旨在鼓励和希望修改软件的第三方也为项目和社区做出贡献。我们认为这是一种更公平的前进方式,我们相信这将有助于我们建立更强大的社区。 + +简单来讲:如果您修改了 `TopIAM` 项目源代码,那么您必须将这些修改贡献给社区,**绝不允许修改后和衍生的代码做为闭源的商业软件发布和销售 +**。 + +我们也提供了商业授权,如果您**需要将本产品进行二次开发、更改并进行任何附带商业化性质行为使用** +,请联系我们进行商业授权,以遵守 `AGPL-3.0` 协议保证您的正常使用。 + +除此之外,我们也会酌情接受根据个人或企业需求的定制化开发。 + +目前在国内 `GPL` 协议**具备合同特征,是一种民事法律行为** ,属于我国《合同法》调整的范围。 `TopIAM` 项目团队保留诉讼权利。 + +[相关案例:违反 `GPL` 协议赔偿 50 万,国内首例!](https://mp.weixin.qq.com/s/YQ6sNjbDS-P7BViLZIsaoA) + +> **TopIAM 开源团队拥有对本开源协议的最终解释权。** + +## 技术架构 + +前端技术栈:`ES6`、`React`、`TypeScript`、`UmiJS`、`Axios`、`Ant Design`; + +后端技术栈:`JDK11`、`Spring Boot`、`Spring Data`、`Spring Security`、`Elasticsearch`、`Redis`、`MySQL`; + +## 联系我们 + +如果你想加入我们的开源交流群, 或在关于 `TopIAM` 产品上的任何想法、意见建议,商务上的合作需求,请扫码添加下方微信进一步沟通。 + +![](https://user-images.githubusercontent.com/30397655/205442835-cfcfbf2e-eefb-4e9f-8f36-72d91c240896.jpg) + +欢迎关注 TopIAM 微信公众号,接收产品最新动态。 + +![](https://user-images.githubusercontent.com/30397655/206887629-faf77f3e-1681-4918-99bf-773ef434f088.png) + +## 参与贡献 + +我们强烈欢迎有兴趣的开发者参与到项目建设中来,同时欢迎大家对项目提出宝贵意见建议和功能需求,项目正在积极开发,欢迎 PR 👏。 + +强烈推荐阅读 [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way)、[《如何向开源社区提问题》](https://github.com/seajs/seajs/issues/545) +和 [《如何有效地报告 Bug》](http://www.chiark.greenend.org.uk/%7Esgtatham/bugs-cn.html)、[《如何向开源项目提交无法解答的问题》](https://zhuanlan.zhihu.com/p/25795393) +,更好的问题更容易获得帮助。 + +## License + +license + +## 捐赠支持 + +如果您觉得我们的开源项目对您有帮助,那就请项目开发者们来一杯咖啡☕️吧!当前我们接受来自于**微信**、**支付宝**或者**码云** +的捐赠,请在捐赠时备注自己的昵称或附言。 + +您的捐赠将用于支付该项目的一些费用支出,并激励开发者们更好的推动项目的发展,同时欢迎捐赠**公网服务器** +用于提高开发基础设施环境及在线演示系统体验。 + +![](https://user-images.githubusercontent.com/30397655/205442840-1b54a95c-3d11-4542-ae51-040f849b26aa.jpg) diff --git a/eiam-alert/pom.xml b/eiam-alert/pom.xml new file mode 100644 index 00000000..638cad41 --- /dev/null +++ b/eiam-alert/pom.xml @@ -0,0 +1,34 @@ + + + + + cn.topiam + eiam + 1.0.0-beta1 + ../pom.xml + + 4.0.0 + + eiam-alert + pom + \ No newline at end of file diff --git a/eiam-application/eiam-application-all/pom.xml b/eiam-application/eiam-application-all/pom.xml new file mode 100644 index 00000000..19358648 --- /dev/null +++ b/eiam-application/eiam-application-all/pom.xml @@ -0,0 +1,67 @@ + + + + + cn.topiam + eiam-application + 1.0.0-beta1 + ../pom.xml + + jar + 4.0.0 + + eiam-application-all + + + + + cn.topiam + eiam-application-cas + ${project.version} + + + + cn.topiam + eiam-application-form + ${project.version} + + + + cn.topiam + eiam-application-jwt + ${project.version} + + + + cn.topiam + eiam-application-oidc + ${project.version} + + + + cn.topiam + eiam-application-saml2 + ${project.version} + + + \ No newline at end of file diff --git a/eiam-application/eiam-application-cas/pom.xml b/eiam-application/eiam-application-cas/pom.xml new file mode 100644 index 00000000..f4b10b75 --- /dev/null +++ b/eiam-application/eiam-application-cas/pom.xml @@ -0,0 +1,41 @@ + + + + + cn.topiam + eiam-application + 1.0.0-beta1 + ../pom.xml + + jar + 4.0.0 + + eiam-application-cas + + + cn.topiam + eiam-application-core + ${project.version} + + + \ No newline at end of file diff --git a/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/AbstractCasApplicationService.java b/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/AbstractCasApplicationService.java new file mode 100644 index 00000000..6d716e7a --- /dev/null +++ b/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/AbstractCasApplicationService.java @@ -0,0 +1,46 @@ +/* + * eiam-application-cas - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.cas; + +import cn.topiam.employee.application.ApplicationService; +import cn.topiam.employee.common.repository.app.AppCertRepository; +import cn.topiam.employee.common.repository.app.AppRepository; + +/** + * CAS 应用配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/23 20:58 + */ +public abstract class AbstractCasApplicationService implements ApplicationService { + + /** + * AppCertRepository + */ + protected final AppCertRepository appCertRepository; + /** + * ApplicationRepository + */ + protected final AppRepository appRepository; + + protected AbstractCasApplicationService(AppCertRepository appCertRepository, + AppRepository appRepository) { + this.appCertRepository = appCertRepository; + this.appRepository = appRepository; + } +} diff --git a/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/CasStandardApplicationServiceImpl.java b/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/CasStandardApplicationServiceImpl.java new file mode 100644 index 00000000..de154eb9 --- /dev/null +++ b/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/CasStandardApplicationServiceImpl.java @@ -0,0 +1,155 @@ +/* + * eiam-application-cas - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.cas; + +import java.util.List; +import java.util.Map; + +import org.springframework.stereotype.Component; + +import cn.topiam.employee.common.enums.app.AppProtocol; +import cn.topiam.employee.common.enums.app.AppType; +import cn.topiam.employee.common.repository.app.AppCertRepository; +import cn.topiam.employee.common.repository.app.AppRepository; + +/** + * Cas 用户应用 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/20 23:20 + */ +@Component +public class CasStandardApplicationServiceImpl extends AbstractCasApplicationService { + + /** + * 更新应用配置 + * + * @param appId {@link String} + * @param config {@link Map} + */ + @Override + public void saveConfig(String appId, Map config) { + } + + /** + * 获取配置 + * + * @param appId {@link String} + * @return {@link Map} + */ + @Override + public Object getConfig(String appId) { + return null; + } + + /** + * 获取应用标志 + * + * @return {@link String} + */ + @Override + public String getCode() { + return "cas"; + } + + /** + * 获取应用名称 + * + * @return {@link String} + */ + @Override + public String getName() { + return "CAS"; + } + + /** + * 获取应用描述 + * + * @return {@link String} + */ + @Override + public String getDescription() { + return "CAS(Central Authentication Service,集中式认证服务,版本 2.0)是一种基于挑战、应答的开源单点登录协议。在集成客户端和服务端之间网络通畅的情况下广泛在企业中使用,有集成简便,扩展性强的优点。"; + } + + /** + * 获取应用类型 + * + * @return {@link AppType} + */ + @Override + public AppType getType() { + return AppType.STANDARD; + } + + /** + * 获取应用协议 + * + * @return {@link AppProtocol} + */ + @Override + public AppProtocol getProtocol() { + return AppProtocol.CAS; + } + + /** + * 获取表单Schema + * + * @return {@link Map} + */ + @Override + public List getFormSchema() { + return null; + } + + /** + * 获取base64图标 + * + * @return {@link String} + */ + @Override + public String getBase64Icon() { + return "data:image/svg+xml;base64,PHN2ZyB0PSIxNjYwOTc4MDU4MDA3IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDMzODIgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjMwMDQiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNOTI5LjI2MTQzNCA3NzEuNDQ3OThIMjUzLjU5MDY2OFYyNTYuMjY2NTExaDY3NS42NzA3NjZWNzUuMTU1OTYzSDI2Ni42ODI5OTZzLTczLjEwNzE2NC00LjY2MzkzNC0xMzQuNzA0NDA1IDUzLjI4NTQ0NWMtNjEuNjk3MTgzIDU3LjgxNjEyMy01NS43MzQwMSAxMjkuNjQwNzA1LTU1LjczNDAxIDEyOS42NDA3MDV2NjkwLjcxMTk1M2g4NTMuMDE2ODUzVjc3MS40NDc5OHoiIGZpbGw9IiMxRjNGNzgiIHAtaWQ9IjMwMDUiPjwvcGF0aD48cGF0aCBkPSJNMTAxMC44ODAyNzcgMTAyMy45NTAwMjlIMC4wMzkyMzNWMjIzLjM1MjQ2M2MtMC4xMzMyNTUtMi43NjUwNDctMi41ODE4MjEtODMuMTAxMzA4IDY2LjAyNzk3OC0xNTIuMzk0MDRDMTM3LjU3NTMxMi0xLjM2NTg2NiAyMjguMjg4ODI2LTAuMDMzMzE0IDIzMi4xNjk4ODYgMC4wMzMzMTRoNzc4LjcxMDM5MXYzMjUuODQyNDFIMzI1Ljg4MTY0M1Y3MDguNTg0ODE0bDY4OS41NDU5NjktMS4yNjU5MjUgMC4wNDk5NzEgMzAuNzgxOTYzaC00LjU5NzMwNnYyODUuODQ5MTc3ek0zMC44Mzc4NTQgOTkzLjE2ODA2Nmg5NDkuMjc3MTE2di0yNTUuMDE3MjQzbC02ODUuMDE1MjkxIDEuMjY1OTI1VjI5NS4wNjA0NDZIOTgwLjA5ODMxM1YzMC43OTg2MjFIMjMxLjgwMzQzNGMtMS4xOTkyOTctMC4wMzMzMTQtODEuOTM1MzI0LTAuODE2MTg4LTE0My44NDkwNDcgNjEuNzk3MTI0LTU5LjI5ODU4OCA1OS44OTgyMzctNTcuMTgzMTYxIDEyOS40MjQxNjYtNTcuMTQ5ODQ3IDEzMC4xMjM3NTVsMC4wMzMzMTQgNzcwLjQ0ODU2NnoiIGZpbGw9IiMyNDNFOTAiIHAtaWQ9IjMwMDYiPjwvcGF0aD48cGF0aCBkPSJNMzI1Ny43OTcwNTcgNDIzLjkwMTYybC02NzYuNTcwMjM5IDUuMzEzNTUzVjI1Ni4yNjY1MTFoNjc2LjU3MDIzOVY3NS4xNTU5NjNIMjU4My44MjUyOTZzLTcyLjk5MDU2NS00LjY2MzkzNC0xMzQuNTU0NDkzIDUzLjI4NTQ0NWMtNjEuNzEzODQgNTcuODE2MTIzLTU1Ljc1MDY2NyAxMjkuNjQwNzA1LTU1Ljc1MDY2NyAxMjkuNjQwNzA1djM0OS4xMTIxMDloNjgwLjA4NDg0NnYxNjYuNzE4OThoLTY4MC4wODQ4NDZ2MTc1LjEzMDcxOGg4NjQuMjkzNTc4VjQyMy45MDE2MnoiIGZpbGw9IiMxRjNGNzgiIHAtaWQ9IjMwMDciPjwvcGF0aD48cGF0aCBkPSJNMzMxNS4zNjMzMjcgMTAyMy45NTAwMjlIMjMzNS4zMDQyNDdhMTUuMzkwOTgyIDE1LjM5MDk4MiAwIDAgMS0xNS4zOTA5ODItMTUuMzkwOTgyVjcyMS45MjY5OTZjMC04LjQ5NTAyMiA2Ljg5NTk1OS0xNS4zOTA5ODIgMTUuMzkwOTgyLTE1LjM5MDk4Mmg2NzYuMDg3MTg4di0zOC4wNjEwMzJIMjMzNS4zMDQyNDdhMTUuMzkwOTgyIDE1LjM5MDk4MiAwIDAgMS0xNS4zOTA5ODItMTUuMzkwOTgydi00MjkuNzQ4MTk0Yy0wLjExNjU5OC0yLjc4MTcwMy0yLjQzMTkwOC04My4xMDEzMDggNjYuMTYxMjM0LTE1Mi4zOTQwNCA3MS4zNTgxODktNzIuMzI0Mjg5IDE2Mi4xODgzMDEtNzEuMDI1MDUgMTY2LjA4NjAxNy03MC45MjUxMDloNzYzLjIwMjgxMWM4LjQ5NTAyMiAwIDE1LjM5MDk4MiA2Ljg5NTk1OSAxNS4zOTA5ODIgMTUuMzkwOTgydjI5NS4wNDM3ODlhMTUuMzkwOTgyIDE1LjM5MDk4MiAwIDAgMS0xNS4zOTA5ODIgMTUuMzkwOTgyaC02NjkuNjI0MzA5djM4LjU3NzM5Nmg2NzAuMjU3MjcxYTE1LjM5MDk4MiAxNS4zOTA5ODIgMCAwIDEgMTUuMzkwOTgyIDE1LjQwNzYzOWwtMC42NDk2MTkgNjI4LjczMTYwMmExNS4zOTA5ODIgMTUuMzkwOTgyIDAgMCAxLTE1LjM3NDMyNSAxNS4zOTA5ODJ6IG0tOTY0LjY2ODA5OC0zMC43ODE5NjNoOTQ5LjI5Mzc3M2wwLjYxNjMwNi01OTcuOTQ5NjM5aC02NzAuMjQwNjE1YTE1LjM5MDk4MiAxNS4zOTA5ODIgMCAwIDEtMTUuMzkwOTgyLTE1LjM5MDk4MnYtNjkuMzc2MDE3YzAtOC40OTUwMjIgNi44OTU5NTktMTUuMzkwOTgyIDE1LjM5MDk4Mi0xNS4zOTA5ODJoNjY5LjYwNzY1MlYzMC43OTg2MjFoLTc0OC4xNzgyODFjLTEuMTQ5MzI3IDAuMDMzMzE0LTgyLjEwMTg5My0wLjc0OTU2MS0xNDMuODE1NzMzIDYxLjc5NzEyNC01OC44NjU1MDggNTkuNDQ4NS01Ny4yOTk3NTkgMTI5LjQ5MDc5My01Ny4yODMxMDIgMTMwLjE5MDM4M3Y0MTQuOTIzNTQ3aDY3Ni4wODcxODhjOC40OTUwMjIgMCAxNS4zOTA5ODIgNi44NzkzMDIgMTUuMzkwOTgyIDE1LjM5MDk4MnY2OC44MjYzMzljMCA4LjQ5NTAyMi02Ljg5NTk1OSAxNS4zOTA5ODItMTUuMzkwOTgyIDE1LjM5MDk4MmgtNjc2LjA4NzE4OHYyNTUuODUwMDg4eiBtLTU3LjUzMjk1NiAyOS43MzI1NzhoLTMxNy44NjM3NTJhMTUuMzkwOTgyIDE1LjM5MDk4MiAwIDAgMS0xMy42NDIwMDYtOC4yNjE4MjZMMTg3Mi4yNDIyMzkgODQzLjYwNTdIMTQ3My4xMjYwOTZhMTExOTIzLjc4NjMyMyAxMTE5MjMuNzg2MzIzIDAgMCAwLTc5Ljk2OTgwOSAxNzAuNDMzNDcgMTUuMzc0MzI1IDE1LjM3NDMyNSAwIDAgMS0xMy45MjUxNzQgOC44NjE0NzRIMTAzOS44Mjk5ODFhMTUuNDU3NjA5IDE1LjQ1NzYwOSAwIDAgMS0xMy4xMjU2NDMtNy4zMjkwMzkgMTUuNDI0Mjk2IDE1LjQyNDI5NiAwIDAgMS0wLjYxNjMwNS0xNS4wMDc4NzNjODMuMTE3OTY1LTE2NC4zNTM2OTkgMTY0LjM4NzAxMy0zMzEuNTA1NzU4IDI0Mi45NzQyOTgtNDkzLjE0NDM4MSA3OC42MzcyNTctMTYxLjczODU2NSAxNTkuOTM5NjE5LTMyOC45NzM5MDkgMjQzLjE1NzUyNS00OTMuNTEwODM0YTE1LjQwNzYzOSAxNS40MDc2MzkgMCAwIDEgMTMuNzQxOTQ4LTguNDQ1MDUyaDI2NS40OTQ0MzdjNy4wNzkxODUgMCAxMy4wNDIzNTggNC43ODA1MzIgMTQuODQxMzA0IDExLjI5MzM4MyA4NS44MTYzODQgMTY0LjIzNzEwMSAxNjkuMDE3NjMzIDMzMC4wMjMyOTQgMjQ5LjUwMzgwNyA0OTAuMzk1OTkyIDgwLjk1MjU2NyAxNjEuMzM4Nzk5IDE2NC42ODY4MzcgMzI4LjE0MTA2MyAyNTAuOTg2MjcxIDQ5My4yNDQzMjNhMTUuMzc0MzI1IDE1LjM3NDMyNSAwIDAgMS0xMy42MjUzNSAyMi41MDM0ODF6IG0tMzA4LjU1MjU0MS0zMC43NjUzMDdoMjgzLjE2NzQxNWMtODIuMjY4NDYyLTE1Ny45NDA3OS0xNjIuMTIxNjczLTMxNy4wNjQyMi0yMzkuNDU5NjkyLTQ3MS4xNzM5MjEtNzkuNTcwMDQ0LTE1OC41NTcwOTUtMTYxLjgyMTg0OS0zMjIuMzk0NDMtMjQ2LjUzODg3Ny00ODQuNzE1OTg3SDE1MzUuNDM5NTg0Yy04MS42MTg4NDMgMTYxLjYwNTMwOS0xNjEuNDM4NzQgMzI1Ljc5MjQzOS0yMzguNjc2ODE3IDQ4NC42MzI3MDItNzUuMDg5MzM2IDE1NC40NDI4MzktMTUyLjYyNzIzNyAzMTMuOTE2MDY1LTIzMS44OTc0NTYgNDcxLjI1NzIwNmgzMDQuNTg4MTk3YzI2LjYxNzczNy01Ni44MTY3MDkgNTMuMjUyMTMxLTExMy42MzM0MTggODAuMDAzMTI0LTE3MC40NTAxMjdhMTUuMzkwOTgyIDE1LjM5MDk4MiAwIDAgMSAxMy45MjUxNzQtOC44MjgxNmg0MTguMjA0OTU4YzUuNzI5OTc2IDAgMTAuOTkzNTU4IDMuMTgxNDY5IDEzLjY0MjAwNiA4LjI2MTgyNmw4OS4zODA5NjIgMTcxLjAxNjQ2MXoiIGZpbGw9IiMyNDNFOTAiIHAtaWQ9IjMwMDgiPjwvcGF0aD48cGF0aCBkPSJNMTUxNi42MzM5MzcgNTkzLjMxOTAxOWgyOTYuNDc2MjgzbC0xNDguMTc5ODQzLTMxMi41NTAxOTktMTQ4LjI5NjQ0IDMxMi41NTAxOTl6IG0yNDEuNjU4NDAzLTUwOS4wNjgzODVsNDM4LjI5MzE4NyA4NjUuMDU5Nzk2aC0xOTkuNzgyOTM5bC04Ni45OTkwMjQtMTY5LjAzNDI5SDE0MjAuMDU3MTkxbC04MC42MzYwODUgMTY5LjAzNDI5SDExMzYuNDIzMzgzbDQyMi4wODYwMTctODY1LjA1OTc5NmgxOTkuNzgyOTR6IiBmaWxsPSIjMUYzRjc4IiBwLWlkPSIzMDA5Ij48L3BhdGg+PHBhdGggZD0iTTE2NjMuOTQ3NjIgMzgxLjc1OTY0Nmw4My42Njc2NDMgMTc1LjExNDA2MUgxNTgwLjI5NjYzNGw4My42NTA5ODYtMTc1LjExNDA2MW0wLTUzLjUzNTI5OGwtMTIwLjI3OTUyNCAyNTEuNzUyNDg4aDI0MC41NTkwNDhsLTEyMC4yNzk1MjQtMjUxLjc1MjQ4OHoiIGZpbGw9IiMyNDNFOTAiIHAtaWQ9IjMwMTAiPjwvcGF0aD48L3N2Zz4="; + } + + /** + * 创建应用 + * + * @param name {@link String} 名称 + * @param remark {@link String} 备注 + */ + @Override + public String create(String name, String remark) { + return ""; + } + + /** + * 删除应用 + * + * @param appId {@link String} 应用ID + */ + @Override + public void delete(String appId) { + + } + + protected CasStandardApplicationServiceImpl(AppCertRepository appCertRepository, + AppRepository appRepository) { + super(appCertRepository, appRepository); + } +} diff --git a/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/package-info.java b/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/package-info.java new file mode 100644 index 00000000..40ef4277 --- /dev/null +++ b/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-application-cas - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.cas; \ No newline at end of file diff --git a/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/package-info.java b/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/package-info.java new file mode 100644 index 00000000..51618995 --- /dev/null +++ b/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-application-cas - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application; \ No newline at end of file diff --git a/eiam-application/eiam-application-core/pom.xml b/eiam-application/eiam-application-core/pom.xml new file mode 100644 index 00000000..6bd5bbf2 --- /dev/null +++ b/eiam-application/eiam-application-core/pom.xml @@ -0,0 +1,34 @@ + + + + + cn.topiam + eiam-application + 1.0.0-beta1 + ../pom.xml + + jar + 4.0.0 + + eiam-application-core + \ No newline at end of file diff --git a/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/AbstractApplicationService.java b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/AbstractApplicationService.java new file mode 100644 index 00000000..f95d28ee --- /dev/null +++ b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/AbstractApplicationService.java @@ -0,0 +1,136 @@ +/* + * eiam-application-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application; + +import java.math.BigInteger; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; + +import org.bouncycastle.asn1.x500.X500Name; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import cn.topiam.employee.common.entity.app.AppCertEntity; +import cn.topiam.employee.common.enums.app.AppCertUsingType; +import cn.topiam.employee.common.repository.app.AppAccessPolicyRepository; +import cn.topiam.employee.common.repository.app.AppAccountRepository; +import cn.topiam.employee.common.repository.app.AppCertRepository; +import cn.topiam.employee.common.repository.app.AppRepository; +import cn.topiam.employee.support.exception.TopIamException; +import cn.topiam.employee.support.util.CertUtils; +import cn.topiam.employee.support.util.RsaUtils; +import static cn.topiam.employee.support.util.CertUtils.encodePem; +import static cn.topiam.employee.support.util.CertUtils.getX500Name; +import static cn.topiam.employee.support.util.RsaUtils.getKeys; + +/** + * AbstractApplicationService + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/31 22:34 + */ +public abstract class AbstractApplicationService implements ApplicationService { + private final Logger logger = LoggerFactory.getLogger(AbstractApplicationService.class); + + /** + * 创建证书 + * + * @param appId {@link Long} + * @param appCode {@link Long} + * @param usingType {@link AppCertUsingType} + */ + public void createCertificate(Long appId, String appCode, AppCertUsingType usingType) { + try { + AppCertEntity config = new AppCertEntity(); + config.setAppId(appId); + //私钥长度 + config.setKeyLong(2048); + //算法 + config.setSignAlgo("SHA256WITHRSA"); + RsaUtils.RsaResult keys = getKeys(config.getKeyLong()); + X500Name x500Name = getX500Name("app_" + appCode, "TopIAM", "Jinan", "Shandong", "CN", + "EIAM"); + //发行者 + config.setIssuer(x500Name.toString()); + //主题 + config.setSubject(x500Name.toString()); + //证书 起始日期 与 结束日期 + LocalDateTime localDateTime = LocalDateTime.now(); + //证书序列号 + config.setSerial(BigInteger.valueOf(System.currentTimeMillis())); + //开始时间 + Date notBefore = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); + config.setBeginDate( + notBefore.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()); + //结束时间 + Date notAfter = Date + .from(localDateTime.plusYears(10).atZone(ZoneId.systemDefault()).toInstant()); + config + .setEndDate(notAfter.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()); + //相差天数 + config + .setValidity((int) ((notAfter.getTime() - notBefore.getTime()) / 1000 / 3600 / 24)); + //生成证书 + String certificate = CertUtils.getCertificate(x500Name, x500Name, config.getSerial(), + notBefore, notAfter, keys.getPublicKey(), keys.getPrivateKey()); + //私钥 + config.setPrivateKey(encodePem(keys.getPrivateKey())); + //公钥 + config.setPublicKey(encodePem(keys.getPublicKey())); + //证书 + config.setCert(certificate); + //使用类型 + config.setUsingType(usingType); + appCertRepository.save(config); + } catch (Exception e) { + logger.error("创建应用证书异常", e); + throw new TopIamException(e.getMessage(), e); + } + } + + /** + * AppCertRepository + */ + protected final AppCertRepository appCertRepository; + + /** + * AppAccountRepository + */ + protected final AppAccountRepository appAccountRepository; + + /** + *AppAccessPolicyRepository + */ + protected final AppAccessPolicyRepository appAccessPolicyRepository; + + /** + * ApplicationRepository + */ + protected final AppRepository appRepository; + + protected AbstractApplicationService(AppCertRepository appCertRepository, + AppAccountRepository appAccountRepository, + AppAccessPolicyRepository appAccessPolicyRepository, + AppRepository appRepository) { + this.appCertRepository = appCertRepository; + this.appAccountRepository = appAccountRepository; + this.appAccessPolicyRepository = appAccessPolicyRepository; + this.appRepository = appRepository; + } +} diff --git a/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/ApplicationService.java b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/ApplicationService.java new file mode 100644 index 00000000..7510efc7 --- /dev/null +++ b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/ApplicationService.java @@ -0,0 +1,118 @@ +/* + * eiam-application-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application; + +import java.util.List; +import java.util.Map; + +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.common.enums.app.AppProtocol; +import cn.topiam.employee.common.enums.app.AppType; + +/** + * 应用接口 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/20 23:20 + */ +public interface ApplicationService { + + /** + * 获取应用标志 + * + * @return {@link String} + */ + String getCode(); + + /** + * 获取应用名称 + * + * @return {@link String} + */ + String getName(); + + /** + * 获取应用描述 + * + * @return {@link String} + */ + String getDescription(); + + /** + * 获取应用类型 + * + * @return {@link String} + */ + AppType getType(); + + /** + * 获取应用协议 + * + * @return {@link AppProtocol} + */ + AppProtocol getProtocol(); + + /** + * 获取表单Schema + * + * @return {@link Map} + */ + List getFormSchema(); + + /** + * 获取base64图标 + * + * @return {@link String} + */ + String getBase64Icon(); + + /** + * 创建应用 + * + * @param name {@link String} 名称 + * @param remark {@link String} 备注 + * @return {@link String} 应用ID + */ + @Transactional(rollbackFor = Exception.class) + String create(String name, String remark); + + /** + * 删除应用 + * + * @param appId {@link String} 应用ID + */ + void delete(String appId); + + /** + * 更新应用配置 + * + * @param appId {@link String} + * @param config {@link Map} + */ + @Transactional(rollbackFor = Exception.class) + void saveConfig(String appId, Map config); + + /** + * 获取配置 + * + * @param appId {@link String} + * @return {@link Map} + */ + Object getConfig(String appId); +} diff --git a/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/ApplicationServiceLoader.java b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/ApplicationServiceLoader.java new file mode 100644 index 00000000..0870820a --- /dev/null +++ b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/ApplicationServiceLoader.java @@ -0,0 +1,104 @@ +/* + * eiam-application-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application; + +import java.util.*; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.*; +import org.springframework.context.annotation.Configuration; + +import cn.topiam.employee.application.exception.AppTemplateNotExistException; + +/** + * 应用服务加载器 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/20 19:08 + */ +@Configuration +public class ApplicationServiceLoader implements ApplicationContextAware { + + private final Logger logger = LoggerFactory + .getLogger(ApplicationServiceLoader.class); + /** + * 用于保存接口实现类名及对应的类 + */ + private Map loadMap = new HashMap<>(16); + /** + * key: code,value:templateImpl + */ + private final Map applicationServiceMap = new HashMap<>(16); + + /** + * Set the ApplicationContext that this object runs in. + * Normally this call will be used to initialize the object. + *

Invoked after population of normal bean properties but before an init callback such + * as {@link InitializingBean#afterPropertiesSet()} + * or a custom init-method. Invoked after {@link ResourceLoaderAware#setResourceLoader}, + * {@link ApplicationEventPublisherAware#setApplicationEventPublisher} and + * {@link MessageSourceAware}, if applicable. + * + * @param applicationContext the ApplicationContext object to be used by this object + * @throws ApplicationContextException in case of context initialization errors + * @throws BeansException if thrown by application context methods + * @see BeanInitializationException + */ + @Override + public void setApplicationContext(org.springframework.context.ApplicationContext applicationContext) throws BeansException { + loadMap = applicationContext.getBeansOfType(ApplicationService.class); + getApplicationServiceList(); + } + + /** + * 获取应用列表 + * + * @return {@link List} + */ + public Set getApplicationServiceList() { + List values = loadMap.values().stream().toList(); + return new HashSet<>(values); + } + + /** + * 根据CODE获取应用 + * + * @param code {@link String} + * @return {@link List} + */ + public ApplicationService getApplicationService(String code) { + ApplicationService impl = applicationServiceMap.get(code); + if (Objects.isNull(impl)) { + for (ApplicationService service : getApplicationServiceList()) { + if (code.equals(service.getCode())) { + applicationServiceMap.put(code, service); + return service; + } + } + } + if (Objects.isNull(impl)) { + throw new AppTemplateNotExistException(); + } + return impl; + } + +} diff --git a/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/Saml2ApplicationService.java b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/Saml2ApplicationService.java new file mode 100644 index 00000000..7efa7959 --- /dev/null +++ b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/Saml2ApplicationService.java @@ -0,0 +1,46 @@ +/* + * eiam-application-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application; + +import cn.topiam.employee.core.protocol.Saml2ProtocolConfig; +import cn.topiam.employee.core.protocol.Saml2SsoModel; + +/** + * 应用接口 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/20 23:20 + */ +public interface Saml2ApplicationService extends ApplicationService { + + /** + * 获取SSO Modal + * + * @param appId {@link String} + * @return {@link Saml2SsoModel} + */ + Saml2SsoModel getSsoModel(String appId); + + /** + * 获取协议配置 + * + * @param appId {@link String} + * @return {@link Saml2ProtocolConfig} + */ + Saml2ProtocolConfig getProtocolConfig(String appId); +} diff --git a/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/SamlRamRoleNameValueType.java b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/SamlRamRoleNameValueType.java new file mode 100644 index 00000000..a22961d9 --- /dev/null +++ b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/SamlRamRoleNameValueType.java @@ -0,0 +1,44 @@ +/* + * eiam-application-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application; + +import com.fasterxml.jackson.annotation.JsonValue; + +import lombok.Getter; + +/** + * Ram 角色名类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/24 22:39 + */ +public enum SamlRamRoleNameValueType { + + /** + * 应用用户名 + */ + APP_USERNAME("app_user.username"); + + @Getter + @JsonValue + private final String code; + + SamlRamRoleNameValueType(String code) { + this.code = code; + } +} diff --git a/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/context/ApplicationContext.java b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/context/ApplicationContext.java new file mode 100644 index 00000000..89806d26 --- /dev/null +++ b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/context/ApplicationContext.java @@ -0,0 +1,71 @@ +/* + * eiam-application-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.context; + +import java.util.Map; + +import lombok.Data; + +/** + * ApplicationContext + * + * @author TopIAM + * Created by support@topiam.cn on 2022/10/29 22:34 + */ +@Data +public final class ApplicationContext { + /** + * 应用ID + */ + private final Long appId; + + /** + * 应用编码 + */ + private final String appCode; + + /** + * 应用模版 + */ + private final String appTemplate; + + /** + * 客户端ID + */ + private final String clientId; + + /** + * 客户端秘钥 + */ + private final String clientSecret; + + /** + * 配置 + */ + private final Map config; + + public ApplicationContext(Long appId, String appCode, String appTemplate, String clientId, + String clientSecret, Map config) { + this.appCode = appCode; + this.appId = appId; + this.appTemplate = appTemplate; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.config = config; + } +} diff --git a/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/context/ApplicationContextHolder.java b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/context/ApplicationContextHolder.java new file mode 100644 index 00000000..17dd4e9f --- /dev/null +++ b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/context/ApplicationContextHolder.java @@ -0,0 +1,62 @@ +/* + * eiam-application-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.context; + +/** + * + * ApplicationContextHolder + * + * @author TopIAM + * Created by support@topiam.cn on 2022/10/29 22:37 + */ +public final class ApplicationContextHolder { + private static final ThreadLocal HOLDER = new ThreadLocal<>(); + + private ApplicationContextHolder() { + } + + /** + * Returns the {@link ApplicationContext} bound to the current thread. + * + * @return the {@link ApplicationContext} + */ + public static ApplicationContext getApplicationContext() { + return HOLDER.get(); + } + + /** + * Bind the given {@link ApplicationContext} to the current thread. + * + * @param applicationContext the {@link ApplicationContext} + */ + public static void setProviderContext(ApplicationContext applicationContext) { + if (applicationContext == null) { + resetProviderContext(); + } else { + HOLDER.set(applicationContext); + } + } + + /** + * Reset the {@link ApplicationContext} bound to the current thread. + */ + public static void resetProviderContext() { + HOLDER.remove(); + } + +} diff --git a/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/context/package-info.java b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/context/package-info.java new file mode 100644 index 00000000..0f0f4da7 --- /dev/null +++ b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/context/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-application-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.context; \ No newline at end of file diff --git a/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppCertNotExistException.java b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppCertNotExistException.java new file mode 100644 index 00000000..62dae71f --- /dev/null +++ b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppCertNotExistException.java @@ -0,0 +1,32 @@ +/* + * eiam-application-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.exception; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 应用证书不存在 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/7/8 22:21 + */ +public class AppCertNotExistException extends TopIamException { + public AppCertNotExistException() { + super("应用证书不存在"); + } +} diff --git a/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppConfigNotExistException.java b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppConfigNotExistException.java new file mode 100644 index 00000000..2fc878b3 --- /dev/null +++ b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppConfigNotExistException.java @@ -0,0 +1,32 @@ +/* + * eiam-application-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.exception; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 应用配置不存在 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/7/8 22:21 + */ +public class AppConfigNotExistException extends TopIamException { + public AppConfigNotExistException() { + super("app_config_not_exist", "应用配置不存在", DEFAULT_STATUS); + } +} diff --git a/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppNotEnableException.java b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppNotEnableException.java new file mode 100644 index 00000000..980210ab --- /dev/null +++ b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppNotEnableException.java @@ -0,0 +1,32 @@ +/* + * eiam-application-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.exception; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 应用未启用 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/7/8 22:23 + */ +public class AppNotEnableException extends TopIamException { + public AppNotEnableException() { + super("app_not_enable", "应用未启用", DEFAULT_STATUS); + } +} diff --git a/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppNotExistException.java b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppNotExistException.java new file mode 100644 index 00000000..904b227c --- /dev/null +++ b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppNotExistException.java @@ -0,0 +1,32 @@ +/* + * eiam-application-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.exception; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 应用不存在异常 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/7/8 22:23 + */ +public class AppNotExistException extends TopIamException { + public AppNotExistException() { + super("app_not_exist", "应用不存在", DEFAULT_STATUS); + } +} diff --git a/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppTemplateNotExistException.java b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppTemplateNotExistException.java new file mode 100644 index 00000000..c8871ea9 --- /dev/null +++ b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/AppTemplateNotExistException.java @@ -0,0 +1,34 @@ +/* + * eiam-application-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.exception; + +import cn.topiam.employee.support.exception.TopIamException; +import static org.springframework.http.HttpStatus.BAD_REQUEST; + +/** + * 应用模版不存在 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/7/8 22:49 + */ +public class AppTemplateNotExistException extends TopIamException { + + public AppTemplateNotExistException() { + super("app_template_not_exist", "应用模版不存在", BAD_REQUEST); + } +} diff --git a/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/ParseSaml2MetadataException.java b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/ParseSaml2MetadataException.java new file mode 100644 index 00000000..ba3c4202 --- /dev/null +++ b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/exception/ParseSaml2MetadataException.java @@ -0,0 +1,31 @@ +/* + * eiam-application-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.exception; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 解析SAML 元数据异常 + * + * @author TopIAM + */ +public class ParseSaml2MetadataException extends TopIamException { + public ParseSaml2MetadataException() { + super("parse_saml2_metadata_error", "解析 SAML 元数据异常", DEFAULT_STATUS); + } +} diff --git a/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/package-info.java b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/package-info.java new file mode 100644 index 00000000..7db6f642 --- /dev/null +++ b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-application-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application; \ No newline at end of file diff --git a/eiam-application/eiam-application-form/pom.xml b/eiam-application/eiam-application-form/pom.xml new file mode 100644 index 00000000..03e10181 --- /dev/null +++ b/eiam-application/eiam-application-form/pom.xml @@ -0,0 +1,41 @@ + + + + + cn.topiam + eiam-application + 1.0.0-beta1 + ../pom.xml + + jar + 4.0.0 + + eiam-application-form + + + cn.topiam + eiam-application-core + ${project.version} + + + \ No newline at end of file diff --git a/eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/AbstractFormApplicationService.java b/eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/AbstractFormApplicationService.java new file mode 100644 index 00000000..f486c3c5 --- /dev/null +++ b/eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/AbstractFormApplicationService.java @@ -0,0 +1,46 @@ +/* + * eiam-application-form - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.form; + +import cn.topiam.employee.application.ApplicationService; +import cn.topiam.employee.common.repository.app.AppCertRepository; +import cn.topiam.employee.common.repository.app.AppRepository; + +/** + * Form 应用配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/23 20:58 + */ +public abstract class AbstractFormApplicationService implements ApplicationService { + + /** + * AppCertRepository + */ + protected final AppCertRepository appCertRepository; + /** + * ApplicationRepository + */ + protected final AppRepository appRepository; + + protected AbstractFormApplicationService(AppCertRepository appCertRepository, + AppRepository appRepository) { + this.appCertRepository = appCertRepository; + this.appRepository = appRepository; + } +} diff --git a/eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/FormStandardApplicationServiceImpl.java b/eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/FormStandardApplicationServiceImpl.java new file mode 100644 index 00000000..f7d10994 --- /dev/null +++ b/eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/FormStandardApplicationServiceImpl.java @@ -0,0 +1,156 @@ +/* + * eiam-application-form - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.form; + +import java.util.List; +import java.util.Map; + +import org.springframework.stereotype.Component; + +import cn.topiam.employee.common.enums.app.AppProtocol; +import cn.topiam.employee.common.enums.app.AppType; +import cn.topiam.employee.common.repository.app.AppCertRepository; +import cn.topiam.employee.common.repository.app.AppRepository; + +/** + * Form 用户应用 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/20 23:20 + */ +@Component +public class FormStandardApplicationServiceImpl extends AbstractFormApplicationService { + + /** + * 更新应用配置 + * + * @param appId {@link String} + * @param config {@link Map} + */ + @Override + public void saveConfig(String appId, Map config) { + } + + /** + * 获取配置 + * + * @param appId {@link String} + * @return {@link Map} + */ + @Override + public Object getConfig(String appId) { + return null; + } + + /** + * 获取应用标志 + * + * @return {@link String} + */ + @Override + public String getCode() { + return "form"; + } + + /** + * 获取应用名称 + * + * @return {@link String} + */ + @Override + public String getName() { + return "表单代填"; + } + + /** + * 获取应用描述 + * + * @return {@link String} + */ + @Override + public String getDescription() { + return "表单代填可以模拟用户在登录页输入用户名和密码,再通过表单提交的一种登录方式。应用的账号密码在 TopIAM 中使用 AES256 加密算法本地加密存储。很多旧系统、不支持标准认证协议的系统或不支持改造的系统可以使用表单代填实现统一身份管理。表单中有图片验证码、CSRF token、动态参数的场景不适用。"; + } + + /** + * 获取应用类型 + * + * @return {@link AppType} + */ + @Override + public AppType getType() { + return AppType.STANDARD; + } + + /** + * 获取应用协议 + * + * @return {@link AppProtocol} + */ + @Override + public AppProtocol getProtocol() { + return AppProtocol.FORM; + } + + /** + * 获取表单Schema + * + * @return {@link Map} + */ + @Override + public List getFormSchema() { + return null; + } + + /** + * 获取base64图标 + * + * @return {@link String} + */ + @Override + public String getBase64Icon() { + return "data:image/svg+xml;base64,PHN2ZyB0PSIxNjYwOTc4MDk3OTkyIiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjMxNDMiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNMjk0LjkxMiA0MTQuMjA4aDg2LjUyOHYzNDIuMDE2SDI5NC45MTJ6TTQyNC45NiA0MTQuMjA4aDMwMy4xMDR2MzQyLjAxNkg0MjQuOTZ6TTI5NC45MTIgMjY3LjI2NGg0MzMuMTUydjk3Ljc5MkgyOTQuOTEyeiIgZmlsbD0iIzI3QTNGRiIgcC1pZD0iMzE0NCI+PC9wYXRoPjxwYXRoIGQ9Ik05MjEuNiAwSDEwMi40YTEwMi40IDEwMi40IDAgMCAwLTEwMi40IDEwMi40djgxOS4yYTEwMi40IDEwMi40IDAgMCAwIDEwMi40IDEwMi40aDgxOS4yYTEwMi40IDEwMi40IDAgMCAwIDEwMi40LTEwMi40VjEwMi40YTEwMi40IDEwMi40IDAgMCAwLTEwMi40LTEwMi40eiBtLTE1MC4wMTYgNzgwLjhhMjMuMDQgMjMuMDQgMCAwIDEtMjEuNTA0IDI0LjA2NEgyNzMuNDA4YTIzLjA0IDIzLjA0IDAgMCAxLTIxLjUwNC0yNC4wNjRWMjQyLjY4OGEyMy4wNCAyMy4wNCAwIDAgMSAyMS41MDQtMjQuMDY0aDQ3Ni42NzJhMjMuMDQgMjMuMDQgMCAwIDEgMjEuNTA0IDI0LjA2NHoiIGZpbGw9IiMyN0EzRkYiIHAtaWQ9IjMxNDUiPjwvcGF0aD48L3N2Zz4="; + } + + /** + * 创建应用 + * + * @param name {@link String} 名称 + * @param remark {@link String} 备注 + */ + @Override + public String create(String name, String remark) { + return ""; + } + + /** + * 删除应用 + * + * @param appId {@link String} 应用ID + */ + @Override + public void delete(String appId) { + + } + + protected FormStandardApplicationServiceImpl(AppCertRepository appCertRepository, + AppRepository appRepository) { + super(appCertRepository, appRepository); + } + +} diff --git a/eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/model/AppFormConfigGetResult.java b/eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/model/AppFormConfigGetResult.java new file mode 100644 index 00000000..fb075e14 --- /dev/null +++ b/eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/model/AppFormConfigGetResult.java @@ -0,0 +1,56 @@ +/* + * eiam-application-form - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.form.model; + +import java.io.Serializable; + +import cn.topiam.employee.common.enums.app.AuthorizationType; +import cn.topiam.employee.common.enums.app.InitLoginType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * Form 配置返回 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/31 22:46 + */ +@Data +@Schema(description = "Form 配置返回结果") +public class AppFormConfigGetResult implements Serializable { + /** + * SSO 发起方 + */ + @Parameter(description = "SSO 发起方") + private InitLoginType initLoginType; + + /** + * SSO 登录链接 + */ + @Parameter(description = "SSO 登录链接") + private String initLoginUrl; + + /** + * 授权范围 + */ + @Parameter(description = "SSO 授权范围") + private AuthorizationType authorizationType; +} diff --git a/eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/model/package-info.java b/eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/model/package-info.java new file mode 100644 index 00000000..11cb26fe --- /dev/null +++ b/eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/model/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-application-form - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.form.model; \ No newline at end of file diff --git a/eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/package-info.java b/eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/package-info.java new file mode 100644 index 00000000..5c44916e --- /dev/null +++ b/eiam-application/eiam-application-form/src/main/java/cn/topiam/employee/application/form/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-application-form - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.form; \ No newline at end of file diff --git a/eiam-application/eiam-application-jwt/pom.xml b/eiam-application/eiam-application-jwt/pom.xml new file mode 100644 index 00000000..63058da2 --- /dev/null +++ b/eiam-application/eiam-application-jwt/pom.xml @@ -0,0 +1,41 @@ + + + + + cn.topiam + eiam-application + 1.0.0-beta1 + ../pom.xml + + jar + 4.0.0 + + eiam-application-jwt + + + cn.topiam + eiam-application-core + ${project.version} + + + \ No newline at end of file diff --git a/eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/AbstractJwtApplicationService.java b/eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/AbstractJwtApplicationService.java new file mode 100644 index 00000000..c6d6d140 --- /dev/null +++ b/eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/AbstractJwtApplicationService.java @@ -0,0 +1,46 @@ +/* + * eiam-application-jwt - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.jwt; + +import cn.topiam.employee.application.ApplicationService; +import cn.topiam.employee.common.repository.app.AppCertRepository; +import cn.topiam.employee.common.repository.app.AppRepository; + +/** + * JWT 应用配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/23 20:58 + */ +public abstract class AbstractJwtApplicationService implements ApplicationService { + + /** + * AppCertRepository + */ + protected final AppCertRepository appCertRepository; + /** + * ApplicationRepository + */ + protected final AppRepository appRepository; + + protected AbstractJwtApplicationService(AppCertRepository appCertRepository, + AppRepository appRepository) { + this.appCertRepository = appCertRepository; + this.appRepository = appRepository; + } +} diff --git a/eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/JwtStandardApplicationServiceImpl.java b/eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/JwtStandardApplicationServiceImpl.java new file mode 100644 index 00000000..cacee9ee --- /dev/null +++ b/eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/JwtStandardApplicationServiceImpl.java @@ -0,0 +1,155 @@ +/* + * eiam-application-jwt - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.jwt; + +import java.util.List; +import java.util.Map; + +import org.springframework.stereotype.Component; + +import cn.topiam.employee.common.enums.app.AppProtocol; +import cn.topiam.employee.common.enums.app.AppType; +import cn.topiam.employee.common.repository.app.AppCertRepository; +import cn.topiam.employee.common.repository.app.AppRepository; + +/** + * JWT 用户应用 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/20 23:20 + */ +@Component +public class JwtStandardApplicationServiceImpl extends AbstractJwtApplicationService { + + /** + * 更新应用配置 + * + * @param appId {@link String} + * @param config {@link Map} + */ + @Override + public void saveConfig(String appId, Map config) { + } + + /** + * 获取配置 + * + * @param appId {@link String} + * @return {@link Map} + */ + @Override + public Object getConfig(String appId) { + return null; + } + + /** + * 获取应用标志 + * + * @return {@link String} + */ + @Override + public String getCode() { + return "jwt"; + } + + /** + * 获取应用名称 + * + * @return {@link String} + */ + @Override + public String getName() { + return "JWT"; + } + + /** + * 获取应用描述 + * + * @return {@link String} + */ + @Override + public String getDescription() { + return "JWT(JSON Web Token)是在网络应用环境声明的一种基于 JSON 的开放标准。TopIAM 使用 JWT 进行分布式站点的单点登录 (SSO)。JWT 单点登录基于非对称加密,由 TopIAM 将用户状态和信息使用私钥加密,传递给应用后,应用使用公钥解密并进行验证。使用场景非常广泛,集成简单。"; + } + + /** + * 获取应用类型 + * + * @return {@link AppType} + */ + @Override + public AppType getType() { + return AppType.STANDARD; + } + + /** + * 获取应用协议 + * + * @return {@link AppProtocol} + */ + @Override + public AppProtocol getProtocol() { + return AppProtocol.JWT; + } + + /** + * 获取表单Schema + * + * @return {@link Map} + */ + @Override + public List getFormSchema() { + return null; + } + + /** + * 获取base64图标 + * + * @return {@link String} + */ + @Override + public String getBase64Icon() { + return "data:image/svg+xml;base64,PHN2ZyB0PSIxNjYwOTc4MjExNDg3IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjM4NzEiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNTg5LjMxMiAyNzUuNDU2TDU4OC4yODggMGgtMTUzLjZsMS4wMjQgMjc1LjQ1NiA3Ni44IDEwNS40NzJ6IG0tMTUzLjYgNDcyLjA2NHYyNzYuNDhoMTUzLjZ2LTI3Ni40OEw1MTIuNTEyIDY0Mi4wNDh6IiBmaWxsPSIjRkZGRkZGIiBwLWlkPSIzODcyIj48L3BhdGg+PHBhdGggZD0iTTU4OS4zMTIgNzQ3LjUybDE2MS43OTIgMjIzLjIzMiAxMjMuOTA0LTkwLjExMi0xNjEuNzkyLTIyMy4yMzItMTIzLjkwNC0zOS45MzZ6IG0tMTUzLjYtNDcyLjA2NEwyNzIuODk2IDUyLjIyNGwtMTIzLjkwNCA5MC4xMTJMMzEwLjc4NCAzNjUuNTY4bDEyNC45MjggMzkuOTM2eiIgZmlsbD0iIzAwRjJFNiIgcC1pZD0iMzg3MyI+PC9wYXRoPjxwYXRoIGQ9Ik0zMTAuNzg0IDM2NS41NjhMNDguNjQgMjgwLjU3NiAxLjUzNiA0MjUuOTg0IDI2My42OCA1MTJsMTIzLjkwNC00MC45NnogbTMyNS42MzIgMTg2LjM2OGw3Ni44IDEwNS40NzIgMjYyLjE0NCA4NC45OTIgNDcuMTA0LTE0NS40MDgtMjYyLjE0NC04NC45OTJ6IiBmaWxsPSIjMDBCOUYxIiBwLWlkPSIzODc0Ij48L3BhdGg+PHBhdGggZD0iTTc2MC4zMiA1MTJsMjYyLjE0NC04Ni4wMTYtNDcuMTA0LTE0NS40MDhMNzEzLjIxNiAzNjUuNTY4bC03Ni44IDEwNS40NzJ6IG0tNDk2LjY0IDBMMS41MzYgNTk2Ljk5MiA0OC42NCA3NDIuNGwyNjIuMTQ0LTg0Ljk5MiA3Ni44LTEwNS40NzJ6IiBmaWxsPSIjRDYzQUZGIiBwLWlkPSIzODc1Ij48L3BhdGg+PHBhdGggZD0iTTMxMC43ODQgNjU3LjQwOEwxNDguOTkyIDg4MC42NGwxMjMuOTA0IDkwLjExMiAxNjIuODE2LTIyMy4yMzJWNjE3LjQ3MnpNNzEzLjIxNiAzNjUuNTY4bDE2MS43OTItMjIzLjIzMi0xMjMuOTA0LTkwLjExMi0xNjEuNzkyIDIyMy4yMzJ2MTMwLjA0OHoiIGZpbGw9IiNGQjAxNUIiIHAtaWQ9IjM4NzYiPjwvcGF0aD48L3N2Zz4="; + } + + /** + * 创建应用 + * + * @param name {@link String} 名称 + * @param remark {@link String} 备注 + */ + @Override + public String create(String name, String remark) { + return ""; + } + + /** + * 删除应用 + * + * @param appId {@link String} 应用ID + */ + @Override + public void delete(String appId) { + + } + + public JwtStandardApplicationServiceImpl(AppCertRepository appCertRepository, + AppRepository appRepository) { + super(appCertRepository, appRepository); + } +} diff --git a/eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/model/AppJwtGetResult.java b/eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/model/AppJwtGetResult.java new file mode 100644 index 00000000..d30f3894 --- /dev/null +++ b/eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/model/AppJwtGetResult.java @@ -0,0 +1,56 @@ +/* + * eiam-application-jwt - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.jwt.model; + +import java.io.Serializable; + +import cn.topiam.employee.common.enums.app.AuthorizationType; +import cn.topiam.employee.common.enums.app.InitLoginType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * JWT 配置返回 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/31 22:46 + */ +@Data +@Schema(description = "JWT 配置返回结果") +public class AppJwtGetResult implements Serializable { + /** + * SSO 发起方 + */ + @Parameter(description = "SSO 发起方") + private InitLoginType initLoginType; + + /** + * SSO 登录链接 + */ + @Parameter(description = "SSO 登录链接") + private String initLoginUrl; + + /** + * 授权范围 + */ + @Parameter(description = "SSO 授权范围") + private AuthorizationType authorizationType; +} diff --git a/eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/model/package-info.java b/eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/model/package-info.java new file mode 100644 index 00000000..8e3b6f36 --- /dev/null +++ b/eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/model/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-application-jwt - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.jwt.model; \ No newline at end of file diff --git a/eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/package-info.java b/eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/package-info.java new file mode 100644 index 00000000..ff315d05 --- /dev/null +++ b/eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/jwt/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-application-jwt - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.jwt; \ No newline at end of file diff --git a/eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/package-info.java b/eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/package-info.java new file mode 100644 index 00000000..f33bdf5b --- /dev/null +++ b/eiam-application/eiam-application-jwt/src/main/java/cn/topiam/employee/application/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-application-jwt - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application; \ No newline at end of file diff --git a/eiam-application/eiam-application-oidc/pom.xml b/eiam-application/eiam-application-oidc/pom.xml new file mode 100644 index 00000000..2448b611 --- /dev/null +++ b/eiam-application/eiam-application-oidc/pom.xml @@ -0,0 +1,41 @@ + + + + + cn.topiam + eiam-application + 1.0.0-beta1 + ../pom.xml + + jar + 4.0.0 + + eiam-application-oidc + + + cn.topiam + eiam-application-core + ${project.version} + + + \ No newline at end of file diff --git a/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/AbstractOidcApplicationService.java b/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/AbstractOidcApplicationService.java new file mode 100644 index 00000000..c1d6495e --- /dev/null +++ b/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/AbstractOidcApplicationService.java @@ -0,0 +1,70 @@ +/* + * eiam-application-oidc - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.oidc; + +import cn.topiam.employee.application.AbstractApplicationService; +import cn.topiam.employee.application.ApplicationService; +import cn.topiam.employee.common.repository.app.*; + +/** + * OIDC 应用配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/23 20:58 + */ +public abstract class AbstractOidcApplicationService extends AbstractApplicationService + implements ApplicationService { + + @Override + public void delete(String appId) { + //删除应用 + appRepository.deleteById(Long.valueOf(appId)); + //删除证书 + appCertRepository.deleteByAppId(Long.valueOf(appId)); + //删除应用账户 + appAccountRepository.deleteAllByAppId(Long.valueOf(appId)); + //删除应用权限策略 + appAccessPolicyRepository.deleteAllByAppId(Long.valueOf(appId)); + //删除SAML2配置 + appOidcConfigRepository.deleteByAppId(Long.valueOf(appId)); + } + + /** + * AppCertRepository + */ + protected final AppCertRepository appCertRepository; + /** + * ApplicationRepository + */ + protected final AppRepository appRepository; + /** + * AppOidcConfigRepository + */ + protected final AppOidcConfigRepository appOidcConfigRepository; + + protected AbstractOidcApplicationService(AppCertRepository appCertRepository, + AppAccountRepository appAccountRepository, + AppAccessPolicyRepository appAccessPolicyRepository, + AppRepository appRepository, + AppOidcConfigRepository appOidcConfigRepository) { + super(appCertRepository, appAccountRepository, appAccessPolicyRepository, appRepository); + this.appCertRepository = appCertRepository; + this.appRepository = appRepository; + this.appOidcConfigRepository = appOidcConfigRepository; + } +} diff --git a/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/OidcStandardApplicationServiceImpl.java b/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/OidcStandardApplicationServiceImpl.java new file mode 100644 index 00000000..360eb0f2 --- /dev/null +++ b/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/OidcStandardApplicationServiceImpl.java @@ -0,0 +1,291 @@ +/* + * eiam-application-oidc - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.oidc; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import javax.validation.ConstraintViolationException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; +import org.springframework.security.oauth2.core.oidc.OidcScopes; +import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; +import org.springframework.stereotype.Component; +import org.springframework.util.AlternativeJdkIdGenerator; +import org.springframework.util.IdGenerator; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Sets; + +import cn.topiam.employee.application.exception.AppNotExistException; +import cn.topiam.employee.application.oidc.converter.AppOidcStandardConfigConverter; +import cn.topiam.employee.application.oidc.model.AppOidcStandardSaveConfigParam; +import cn.topiam.employee.audit.context.AuditContext; +import cn.topiam.employee.common.entity.app.AppEntity; +import cn.topiam.employee.common.entity.app.AppOidcConfigEntity; +import cn.topiam.employee.common.entity.app.po.AppOidcConfigPO; +import cn.topiam.employee.common.enums.app.*; +import cn.topiam.employee.common.repository.app.*; +import cn.topiam.employee.support.exception.TopIamException; +import cn.topiam.employee.support.util.BeanUtils; +import cn.topiam.employee.support.validation.ValidationHelp; +import static org.springframework.security.oauth2.core.ClientAuthenticationMethod.NONE; + +import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; + +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_BY; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_TIME; + +/** + * OIDC 用户应用 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/20 23:20 + */ +@SuppressWarnings("DuplicatedCode") +@Component +public class OidcStandardApplicationServiceImpl extends AbstractOidcApplicationService { + private final Logger logger = LoggerFactory.getLogger(OidcStandardApplicationServiceImpl.class); + + /** + * 创建应用 + * + * @param name {@link String} 名称 + * @param remark {@link String} 备注 + */ + @Override + public String create(String name, String remark) { + //1、创建应用 + AppEntity appEntity = new AppEntity(); + appEntity.setName(name); + appEntity.setCode( + org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric(32).toLowerCase()); + appEntity.setTemplate(getCode()); + appEntity.setType(AppType.STANDARD); + appEntity.setEnabled(true); + appEntity.setProtocol(getProtocol()); + appEntity.setClientId(idGenerator.generateId().toString().replace("-", "")); + appEntity.setClientSecret(idGenerator.generateId().toString().replace("-", "")); + appEntity.setInitLoginType(InitLoginType.APP); + appEntity.setAuthorizationType(AuthorizationType.AUTHORIZATION); + appEntity.setRemark(remark); + appRepository.save(appEntity); + //2、创建证书 + createCertificate(appEntity.getId(), appEntity.getCode(), AppCertUsingType.OIDC_JWK); + //3、创建配置 + AppOidcConfigEntity entity = new AppOidcConfigEntity(); + entity.setAppId(appEntity.getId()); + //客户端认证方法 + ClientAuthenticationMethod clientSecretBasic = ClientAuthenticationMethod.CLIENT_SECRET_BASIC; + ClientAuthenticationMethod clientSecretPost = ClientAuthenticationMethod.CLIENT_SECRET_POST; + entity.setClientAuthMethods( + Sets.newHashSet(clientSecretBasic.getValue(), clientSecretPost.getValue())); + //授予类型 + AuthorizationGrantType authorizationCodeGrantType = AuthorizationGrantType.AUTHORIZATION_CODE; + AuthorizationGrantType refreshTokenGrantType = AuthorizationGrantType.REFRESH_TOKEN; + AuthorizationGrantType clientCredentialsGrantType = AuthorizationGrantType.CLIENT_CREDENTIALS; + entity.setAuthGrantTypes(Sets.newHashSet(authorizationCodeGrantType.getValue(), + refreshTokenGrantType.getValue(), clientCredentialsGrantType.getValue())); + entity.setResponseTypes(Sets.newHashSet(OAuth2AuthorizationResponseType.CODE.getValue())); + //重定向URLs + entity.setRedirectUris(Sets.newLinkedHashSet()); + //Scopes + entity.setGrantScopes(Sets.newHashSet(OidcScopes.OPENID)); + //授权是否需要同意 + entity.setRequireAuthConsent(false); + //PKCE + entity.setRequireProofKey(false); + //Token 端点认证签名算法 + entity.setTokenEndpointAuthSigningAlgorithm(SignatureAlgorithm.RS256.getName()); + //刷新 Token 过期时间 默认(30天) + entity.setRefreshTokenTimeToLive(43200); + //访问 Token 过期时间 默认(15天) + entity.setAccessTokenTimeToLive(21600); + //Access Token 格式 + entity.setAccessTokenFormat("reference"); + //30分钟 + entity.setIdTokenTimeToLive(30); + //ID Token签名算法 + entity.setIdTokenSignatureAlgorithm(SignatureAlgorithm.RS256.getName()); + //是否重用刷新令牌 + entity.setReuseRefreshToken(false); + appOidcConfigRepository.save(entity); + return entity.getAppId().toString(); + } + + /** + * 更新应用配置 + * + * @param appId {@link String} + * @param config {@link Map} + */ + @Override + public void saveConfig(String appId, Map config) { + AppOidcStandardSaveConfigParam model; + try { + ObjectMapper mapper = new ObjectMapper(); + String value = mapper.writeValueAsString(config); + // 指定序列化输入的类型 + mapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false); + model = mapper.readValue(value, AppOidcStandardSaveConfigParam.class); + } catch (Exception e) { + throw new TopIamException(e.getMessage()); + } + //@formatter:off + ValidationHelp.ValidationResult validationResult = ValidationHelp.validateEntity(model); + if (validationResult.isHasErrors()) { + throw new ConstraintViolationException(validationResult.getConstraintViolations()); + } + //@formatter:on + //1、修改基本信息 + Optional optional = appRepository.findById(Long.valueOf(appId)); + if (optional.isEmpty()) { + AuditContext.setContent("保存配置失败,应用 [" + appId + "] 不存在!"); + logger.error(AuditContext.getContent()); + throw new AppNotExistException(); + } + AppEntity appEntity = optional.get(); + appEntity.setAuthorizationType(model.getAuthorizationType()); + appEntity.setInitLoginUrl(model.getInitLoginUrl()); + appEntity.setInitLoginType(model.getInitLoginType()); + appRepository.save(appEntity); + //2、修改 OIDC 配置 + Optional oidc = appOidcConfigRepository + .findByAppId(Long.valueOf(appId)); + if (oidc.isEmpty()) { + AuditContext.setContent("保存配置失败,应用 [" + appId + "] 不存在!"); + logger.error(AuditContext.getContent()); + throw new AppNotExistException(); + } + AppOidcConfigEntity entity = oidc.get(); + AppOidcConfigEntity oidcConfig = appOidcStandardConfigConverter + .appOidcStandardSaveConfigParamToEntity(model); + BeanUtils.merge(oidcConfig, entity, LAST_MODIFIED_BY, LAST_MODIFIED_TIME); + //开启PKCE + if (entity.getRequireProofKey()) { + entity.getClientAuthMethods().add(NONE.getValue()); + } else { + entity.getClientAuthMethods().remove(NONE.getValue()); + } + appOidcConfigRepository.save(entity); + } + + /** + * 获取配置 + * + * @param appId {@link String} + * @return {@link AppOidcConfigPO} + */ + @Override + public Object getConfig(String appId) { + AppOidcConfigPO po = appOidcConfigRepository.getByAppId(Long.valueOf(appId)); + return appOidcStandardConfigConverter.entityConverterToOidcConfigResult(po); + } + + /** + * 获取应用标志 + * + * @return {@link String} + */ + @Override + public String getCode() { + return "oidc"; + } + + /** + * 获取应用名称 + * + * @return {@link String} + */ + @Override + public String getName() { + return "OIDC"; + } + + /** + * 获取应用描述 + * + * @return {@link String} + */ + @Override + public String getDescription() { + return "OIDC是OpenID Connect的简称,OIDC=(Identity, Authentication) + OAuth 2.0。它在OAuth2上构建了一个身份层,是一个基于OAuth2协议的身份认证标准协议。OIDC是一个协议族,提供很多的标准协议,包括Core核心协议和一些扩展协议。"; + } + + /** + * 获取应用类型 + * + * @return {@link AppType} + */ + @Override + public AppType getType() { + return AppType.STANDARD; + } + + /** + * 获取应用协议 + * + * @return {@link AppProtocol} + */ + @Override + public AppProtocol getProtocol() { + return AppProtocol.OIDC; + } + + /** + * 获取表单Schema + * + * @return {@link Map} + */ + @Override + public List getFormSchema() { + return null; + } + + /** + * 获取base64图标 + * + * @return {@link String} + */ + @Override + public String getBase64Icon() { + return "data:image/svg+xml;base64,PHN2ZyB0PSIxNjYwOTc4MzEzNTg0IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQ0MzIiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNDY4LjA4OTI2MjIzIDg5NS4xODMzMjc5OWEyMjEuNzI4NTE1NTYgMjIxLjcyODUxNTU2IDAgMCAxLTY1LjAzNzYwMzU3LTcuNzY3MjI5NjMgNTQ0Ljk0ODgzMDgyIDU0NC45NDg4MzA4MiAwIDAgMS0xODguNDg0NzcxNTUtNzAuMzE5MzE4NTFjLTUxLjc4MTUzMTI3LTMyLjEwNDU0ODczLTk2LjEwNjUyMDg5LTcyLjQ5NDE0MjgxLTExNy45NTgzMjc3LTEzMC44MDAxNDY5NmExNzIuNTM2MDYwNDQgMTcyLjUzNjA2MDQ0IDAgMCAxIDYuMDA2NjU3MTgtMTQxLjc3NzgzMTExYzM4LjczMjU4NTQ5LTc4LjA4NjU0ODE0IDEwNy4zOTQ4OTU0MS0xMjAuNTQ3NDAzODUgMTg0LjEzNTEyNDE2LTE1My4wNjYyMDU2M2E1OTkuNjMwMTI3NDEgNTk5LjYzMDEyNzQxIDAgMCAxIDE1OS4yNzk5ODkzMy00MC43MDAyODMyNiAxNDguNzE2NTU3MDUgMTQ4LjcxNjU1NzA1IDAgMCAxIDIxLjQzNzU1Mzc4IDAuNjIxMzc4MzZ2NzguMjkzNjc0NjdhMzg5LjcwNzgwMDg5IDM4OS43MDc4MDA4OSAwIDAgMC05OC41OTIwMzU1NyAyMy45MjMwNjcyNmMtNDkuNzEwMjY5NjMgMTguNDM0MjI0NTktOTQuMjQyMzg1NzcgNDQuNDI4NTUzNDgtMTI2LjY1NzYyMzY4IDg3LjcxNzkxMjg4YTEyNi42NTc2MjQ5IDEyNi42NTc2MjQ5IDAgMCAwLTIwLjcxMjYxMjc2IDEyNC4yNzU2NzQwOGMxNy43MDkyODM1NSA1MC4zMzE2NDggNTcuMDYzMjQ3NDEgODEuOTE4MzgyMjMgMTAxLjgwMjQ5MDA5IDEwNi4xNTIxMzg2OGEzNzguMzE1ODY0OSAzNzguMzE1ODY0OSAwIDAgMCAxNDQuOTg4Mjg1NjEgNDMuNjAwMDQ4NTd6TTYwMC4zMzkyOTEyNSAzNTEuMzczNjkxMjVjMjUuMjY5Mzg2NjYtMy41MjExNDM2OSA0OS4zOTk1ODA0NSAzLjkzNTM5Njc1IDczLjczNjkwMDc2IDguMjg1MDQ1MzZhNTUyLjcxNjA2MDQ1IDU1Mi43MTYwNjA0NSAwIDAgMSAxMzQuNjMxOTc5ODQgNDQuMDE0MzAwNDFjMTIuMTE2ODc4MjIgNS41OTI0MDUzMyAyMy42MTIzNzgwOCAxMi41MzExMzAwNiAzNS40MTg1NjcxMiAxOC44NDg0Nzc2NWExMi43MzgyNTY1OSAxMi43MzgyNTY1OSAwIDAgMCAxNC44MDk1MTgyMiAwIDgyNC4yNTg0MDgzIDgyNC4yNTg0MDgzIDAgMCAxIDcxLjc2OTIwMTc5LTQ0Ljg0MjgwNTMzYzQuMTQyNTIyMDYgMS42NTcwMDg1OSAyLjc5NjIwMjY3IDUuNDg4ODQyNjggMi43OTYyMDI2NiA4LjU5NTczMzMydjEzNC42MzE5ODEwNWMwIDguODAyODU5ODQtMS44NjQxMzUxMSAxMi45NDUzODMxMS0xMS45MDk3NTI5MSAxMi45NDUzODE5MUg2ODkuNzE0MjEzOTJhODEuNjA3NjkzMDUgODEuNjA3NjkzMDUgMCAwIDEtOS43MzQ5MjczOS0xLjU1MzQ0NTkzYzE0LjA4NDU3NTk5LTkuMjE3MTEyOSAyNS4xNjU4MjQtMTYuNjczNjUzMzQgMzYuNDU0MTk3MzItMjMuODE5NTAzMzhzMjIuNjgwMzEwNTItMTQuMDg0NTc1OTkgMzQuMTc1ODEwMzctMjAuNzEyNjEyNzZjNy40NTY1NDA0NS00LjQ1MzIxMTI1IDcuMDQyMjg4NjEtNy42NjM2NjY5OC0wLjYyMTM3ODM3LTExLjU5OTA2MjVhNDE2Ljg0MTMyMzg2IDQxNi44NDEzMjM4NiAwIDAgMC0xMzkuNDk5NDQ0MTUtNDUuNTY3NzQ3NTdjLTMuNzI4MjcwMjIgMC03Ljc2NzIyOTYzIDAtOS43MzQ5MjczOS00LjQ1MzIxMTI1cTAuMTAzNTYyNjYtMzcuMjgyNzAyMjItMC40MTQyNTMwNi03NC43NzI1MzA5OHoiIGZpbGw9IiNDRENDQ0MiIHAtaWQ9IjQ0MzMiPjwvcGF0aD48cGF0aCBkPSJNNTg0Ljc4OTMzMyAzNzkuNjQ4cTAgMzAuODA1MzMzIDAuNjgyNjY3IDYxLjg2NjY2N2MtMy4yNDI2NjcgMy43NTQ2NjctMi4wNDggOC41MzMzMzMtMi4wNDggMTIuNDU4NjY2djI5OC42NjY2NjdhMTUuNTMwNjY3IDE1LjUzMDY2NyAwIDAgMS03LjY4IDE1LjUzMDY2N2MtMzEuOTE0NjY3IDE5LjYyNjY2Ny02My41NzMzMzMgMzkuNTk0NjY3LTk1LjQwMjY2NyA1OS43MzMzMzNhMTUuMTg5MzMzIDE1LjE4OTMzMyAwIDAgMS00LjUyMjY2NiAwLjY4MjY2N2wtMC43NjgtNjYuMjE4NjY3YzIuNTYtMy4xNTczMzMgMS41MzYtNi44MjY2NjcgMS41MzYtMTAuMjRWNDU0LjRjMC0zLjQ5ODY2NyAxLjAyNC03LjE2OC0xLjUzNi0xMC4yNHYtNjQuNTEyYTg1MS42MjY2NjcgODUxLjYyNjY2NyAwIDAgMCAwLjY4MjY2Ni05NS42NTg2NjdjLTAuNDI2NjY3LTE1LjAxODY2NyAyLjIxODY2Ny0yNS4wODggMTcuMDY2NjY3LTMzLjQ1MDY2NiAyNy4zMDY2NjctMTUuNDQ1MzMzIDUyLjk5Mi0zNC4xMzMzMzMgNzkuNDQ1MzMzLTUxLjIgMi41Ni0xLjYyMTMzMyA2LjIyOTMzMy0zLjY2OTMzMyA4LjUzMzMzNC0yLjk4NjY2NyA0LjE4MTMzMyAxLjM2NTMzMyAyLjM4OTMzMyA2LjE0NCAyLjM4OTMzMyA5LjM4NjY2N3YxNTguNDY0YTQwLjE5MiA0MC4xOTIgMCAwIDAgMS42MjEzMzMgMTUuNDQ1MzMzeiBtLTUuODAyNjY2IDEwMC4wOTZWMjI3LjMyOGMwLTUuNTQ2NjY3LTEuMTA5MzMzLTExLjAwOC0wLjg1MzMzNC0xNi40NjkzMzNzLTIuNjQ1MzMzLTUuOTczMzMzLTYuNC0zLjU4NGMtMTguMDA1MzMzIDExLjI2NC0zNS40MTMzMzMgMjMuODA4LTU0LjEwMTMzMyAzNC4xMzMzMzMtMTMuMDU2IDYuOTk3MzMzLTE5LjM3MDY2NyAyMS41MDQtMzMuNzA2NjY3IDI2LjUzODY2N2E3LjE2OCA3LjE2OCAwIDAgMC00LjE4MTMzMyA4Ljk2YzUuMzc2IDEyLjI4OCAxLjQ1MDY2NyAyNC44MzIgMS41MzYgMzcuMjA1MzMzdjQ5MS43NzZjMCA1LjAzNDY2NyAxLjM2NTMzMyA1LjcxNzMzMyA2LjkxMiA1LjcxNzMzMyAxNC4yNTA2NjcgMCAyMS43Ni0xMS45NDY2NjcgMzMuNDUwNjY3LTE2LjM4NCAxMy43Mzg2NjctNS4yMDUzMzMgMjIuNTI4LTE4LjY4OCAzNy42MzItMjMuMjEwNjY2YTI0LjQwNTMzMyAyNC40MDUzMzMgMCAwIDAgMTkuMDI5MzMzLTE3LjU3ODY2NyAyMDMuNjA1MzMzIDIwMy42MDUzMzMgMCAwIDAgMC41MTItMjcuOTg5MzMzeiIgZmlsbD0iI0U3NzIyQSIgcC1pZD0iNDQzNCI+PC9wYXRoPjxwYXRoIGQ9Ik01OTMuMjk3MDAzODYgNDcyLjg1MzE2MjY3Vjc3MS45NDMyODUzNGEyNDcuMTAxNDY0ODkgMjQ3LjEwMTQ2NDg5IDAgMCAxLTAuNjIxMzc4MzcgMzMuOTY4NjgzODQgMjkuNjE5MDM1MjUgMjkuNjE5MDM1MjUgMCAwIDEtMjMuMDk0NTYzNTcgMjEuMzMzOTkxMTNjLTE4LjMzMDY2MTkyIDUuNDg4ODQyNjgtMjguOTk3NjU2ODkgMjEuODUxODA1NjItNDUuNjcxMzEwMjMgMjguMTY5MTUxOTgtMTQuMTg4MTM5ODYgNS4zODUyNzg4MS0yMy4zMDE2ODg4OSAxOS45ODc2NzA1MS00MC41OTY3MTkzOCAxOS44ODQxMDc4NS02LjczMTU5OTQxIDAtOC41OTU3MzQ1My0wLjgyODUwNDktOC4zODg2MDgtNi45Mzg3MjQ3M1Y4NTYuODY0OTk1NTUgMjcxLjUyNjU3MDY3YzAtMTUuMDE2NjQzNTUgNC42NjAzMzc3OC0zMC4yNDA0MTM2Mi0xLjg2NDEzNTEyLTQ1LjE1MzQ5NDUxYTguNjk5Mjk3MTggOC42OTkyOTcxOCAwIDAgMSA1LjA3NDU4OTYzLTEwLjg3NDEyMTQ4YzE3LjM5ODU5NDM3LTYuMTEwMjIxMDUgMjUuMDYyMjYxMzQtMjMuNzE1OTQwNzMgNDAuOTA3NDA5NzktMzIuMjA4MTEyNiAyMi42ODAzMTA1Mi0xMi4yMjA0NDA4OSA0My44MDcxNzUxMS0yNy40NDQyMTA5NSA2NS42NTg5ODA3Mi00MS40MjUyMjQzIDQuNTU2Nzc1MTEtMi44OTk3NjUzMiA4LjA3NzkxODgyLTMuMjEwNDU0NTEgNy43NjcyMjk2NCA0LjM0OTY0ODZzMS4wMzU2MzAyMiAxMy4yNTYwNzIzMSAxLjAzNTYzMDIgMTkuOTg3NjcwNTFxMCAxNTMuNDgwNDU3NDgtMC4yMDcxMjUzMSAzMDYuNjUwMjI1Nzh6IiBmaWxsPSIjRkQ2MjAyIiBwLWlkPSI0NDM1Ij48L3BhdGg+PC9zdmc+"; + } + + /** + * IdGenerator + */ + private final IdGenerator idGenerator = new AlternativeJdkIdGenerator(); + private final AppOidcStandardConfigConverter appOidcStandardConfigConverter; + + protected OidcStandardApplicationServiceImpl(AppCertRepository appCertRepository, + AppAccountRepository appAccountRepository, + AppAccessPolicyRepository appAccessPolicyRepository, + AppRepository appRepository, + AppOidcConfigRepository appOidcConfigRepository, + AppOidcStandardConfigConverter appOidcStandardConfigConverter) { + super(appCertRepository, appAccountRepository, appAccessPolicyRepository, appRepository, + appOidcConfigRepository); + this.appOidcStandardConfigConverter = appOidcStandardConfigConverter; + } +} diff --git a/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/converter/AppOidcStandardConfigConverter.java b/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/converter/AppOidcStandardConfigConverter.java new file mode 100644 index 00000000..c0175269 --- /dev/null +++ b/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/converter/AppOidcStandardConfigConverter.java @@ -0,0 +1,130 @@ +/* + * eiam-application-oidc - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.oidc.converter; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.apache.commons.text.StringSubstitutor; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import cn.topiam.employee.application.oidc.model.AppOidcStandardConfigGetResult; +import cn.topiam.employee.application.oidc.model.AppOidcStandardSaveConfigParam; +import cn.topiam.employee.common.constants.ProtocolConstants; +import cn.topiam.employee.common.entity.app.AppOidcConfigEntity; +import cn.topiam.employee.common.entity.app.po.AppOidcConfigPO; +import cn.topiam.employee.core.context.ServerContextHelp; +import cn.topiam.employee.support.util.HttpUrlUtils; +import static cn.topiam.employee.common.constants.ProtocolConstants.APP_CODE; +import static cn.topiam.employee.common.constants.ProtocolConstants.OidcEndpointConstants.OIDC_AUTHORIZE_PATH; +import static cn.topiam.employee.common.constants.ProtocolConstants.OidcEndpointConstants.WELL_KNOWN_OPENID_CONFIGURATION; + +/** + * 应用映射 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/14 22:45 + */ +@Mapper(componentModel = "spring") +public interface AppOidcStandardConfigConverter { + /** + * 实体转OIDC配置结果 + * + * @param config {@link AppOidcConfigEntity} + * @return {@link AppOidcStandardConfigGetResult} + */ + default AppOidcStandardConfigGetResult entityConverterToOidcConfigResult(AppOidcConfigPO config) { + AppOidcStandardConfigGetResult result = new AppOidcStandardConfigGetResult(); + if (Objects.isNull(config)) { + return result; + } + //协议端点域 + result.setProtocolEndpoint(getProtocolEndpointDomain(config.getAppCode())); + //认证授权类型 + result.setAuthGrantTypes(config.getAuthGrantTypes()); + //重定向URI + result.setRedirectUris(config.getRedirectUris()); + //授权范围 + result.setGrantScopes(config.getGrantScopes()); + //启用PKCE + result.setRequireProofKey(config.getRequireProofKey()); + //访问令牌有效时间 + result.setAccessTokenTimeToLive(config.getAccessTokenTimeToLive().toString()); + //刷新令牌有效时间 + result.setRefreshTokenTimeToLive(config.getRefreshTokenTimeToLive().toString()); + //ID令牌有效时间 + result.setIdTokenTimeToLive(config.getIdTokenTimeToLive().toString()); + // id 令牌签名算法 + result.setIdTokenSignatureAlgorithm(config.getIdTokenSignatureAlgorithm()); + //SSO 发起方 + result.setInitLoginType(config.getInitLoginType()); + //登录发起地址 + result.setInitLoginUrl(config.getInitLoginUrl()); + //授权类型 + result.setAuthorizationType(config.getAuthorizationType()); + return result; + } + + /** + * save 转 entity + * + * @param config {@link AppOidcConfigEntity} + * @return {@link AppOidcConfigEntity} + */ + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "remark", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + @Mapping(target = "appId", ignore = true) + AppOidcConfigEntity appOidcStandardSaveConfigParamToEntity(AppOidcStandardSaveConfigParam config); + + /** + * 获取协议端点 + * + * @param appCode {@link String} + * @return {@link AppOidcStandardConfigGetResult.ProtocolEndpoint} + */ + private AppOidcStandardConfigGetResult.ProtocolEndpoint getProtocolEndpointDomain(String appCode) { + //@formatter:off + AppOidcStandardConfigGetResult.ProtocolEndpoint domain = new AppOidcStandardConfigGetResult.ProtocolEndpoint(); + //issues + Map variables = new HashMap<>(16); + variables.put(APP_CODE,appCode); + StringSubstitutor sub = new StringSubstitutor(variables, "{", "}"); + //Issuer + domain.setIssuer(sub.replace(ServerContextHelp.getPortalPublicBaseUrl()+OIDC_AUTHORIZE_PATH)); + //发现端点 + domain.setDiscoveryEndpoint(HttpUrlUtils.format(ServerContextHelp.getPortalPublicBaseUrl() + sub.replace(WELL_KNOWN_OPENID_CONFIGURATION))); + //认证端点 + domain.setAuthorizationEndpoint(HttpUrlUtils.format(ServerContextHelp.getPortalPublicBaseUrl() + sub.replace(ProtocolConstants.OidcEndpointConstants.AUTHORIZATION_ENDPOINT))); + //Token端点 + domain.setTokenEndpoint(HttpUrlUtils.format(ServerContextHelp.getPortalPublicBaseUrl() + sub.replace( ProtocolConstants.OidcEndpointConstants.TOKEN_ENDPOINT))); + //Jwks端点 + domain.setJwksEndpoint(HttpUrlUtils.format(ServerContextHelp.getPortalPublicBaseUrl() + sub.replace(ProtocolConstants.OidcEndpointConstants.JWK_SET_ENDPOINT))); + //撤销端点 + domain.setRevokeEndpoint(HttpUrlUtils.format(ServerContextHelp.getPortalPublicBaseUrl()+ sub.replace(ProtocolConstants.OidcEndpointConstants.TOKEN_REVOCATION_ENDPOINT))); + //UserInfo端点 + domain.setUserinfoEndpoint(HttpUrlUtils.format(ServerContextHelp.getPortalPublicBaseUrl() + sub.replace(ProtocolConstants.OidcEndpointConstants.OIDC_USER_INFO_ENDPOINT))); + return domain; + //@formatter:on + } +} diff --git a/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/model/AppOidcStandardConfigGetResult.java b/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/model/AppOidcStandardConfigGetResult.java new file mode 100644 index 00000000..3a45480f --- /dev/null +++ b/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/model/AppOidcStandardConfigGetResult.java @@ -0,0 +1,202 @@ +/* + * eiam-application-oidc - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.oidc.model; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Set; + +import cn.topiam.employee.common.enums.app.AuthorizationType; +import cn.topiam.employee.common.enums.app.InitLoginType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * OIDC 配置返回 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/31 22:46 + */ +@Data +@Schema(description = "OIDC 配置返回结果") +public class AppOidcStandardConfigGetResult implements Serializable { + + @Serial + private static final long serialVersionUID = 4177874005424703372L; + + /** + * APP ID + */ + @Parameter(description = "appId") + private Long appId; + + /** + * SSO 发起方 + */ + @Parameter(description = "SSO 发起方") + private InitLoginType initLoginType; + + /** + * SSO 登录链接 + */ + @Parameter(description = "SSO 登录链接") + private String initLoginUrl; + + /** + * 授权范围 + */ + @Parameter(description = "SSO 授权范围") + private AuthorizationType authorizationType; + /** + * authorizationGrantTypes + */ + @Parameter(description = "认证授权类型") + private Set authGrantTypes; + + /** + * 客户端认证方式 + */ + @Parameter(description = "客户端认证方式") + private Set clientAuthMethods; + + /** + * 重定向URI + */ + @Parameter(description = "重定向URI") + private Set redirectUris; + /** + * scopes + */ + @Parameter(description = "授权范围") + private Set grantScopes; + + /** + * 启用PKCE + */ + @Parameter(description = "启用PKCE") + private Boolean requireProofKey; + + /** + * 令牌 Endpoint 身份验证签名算法 + */ + @Parameter(description = "令牌 Endpoint 身份验证签名算法") + private String tokenEndpointAuthSigningAlgorithm; + + /** + * 是否需要授权同意 + */ + @Parameter(description = "是否需要授权同意") + private Boolean requireAuthConsent; + /** + * 访问令牌有效时间 + */ + @Parameter(description = "访问令牌有效时间") + private String accessTokenTimeToLive; + /** + * 刷新令牌有效时间 + */ + @Parameter(description = "刷新令牌有效时间") + private String refreshTokenTimeToLive; + + /** + * ID token 有效时间 + */ + @Parameter(description = "ID 令牌有效时间") + private String idTokenTimeToLive; + /** + * id 令牌签名算法 + */ + @Parameter(description = "Id令牌签名算法") + private String idTokenSignatureAlgorithm; + + /** + * 协议端点域 + */ + @Parameter(description = "协议端点域") + private ProtocolEndpoint protocolEndpoint; + + /** + * Access Token 格式 + */ + @Parameter(description = "Access Token 格式") + private String accessTokenFormat; + + /** + * 是否重用刷新令牌 + */ + @Parameter(description = "是否重用刷新令牌") + private Boolean reuseRefreshToken; + + /** + * 协议端点域 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/4 23:37 + */ + @Data + @Schema(description = "协议端点") + public static class ProtocolEndpoint implements Serializable { + + @Serial + private static final long serialVersionUID = -2261602995152894964L; + /** + * oidcIssuer + */ + @Parameter(description = "Issuer") + private String issuer; + + /** + * discoveryEndpoint + */ + @Parameter(description = "Discovery Endpoint") + private String discoveryEndpoint; + + /** + * UserinfoEndpoint + */ + @Parameter(description = "UserInfo Endpoint") + private String userinfoEndpoint; + + /** + * jwksEndpoint + */ + @Parameter(description = "Jwks Endpoint") + private String jwksEndpoint; + + /** + * revokeEndpoint + */ + @Parameter(description = "Revoke Endpoint") + private String revokeEndpoint; + + /** + * tokenEndpoint + */ + @Parameter(description = "Token Endpoint") + private String tokenEndpoint; + + /** + * authorizationEndpoint + */ + @Parameter(description = "Authorization Endpoint") + private String authorizationEndpoint; + } +} diff --git a/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/model/AppOidcStandardSaveConfigParam.java b/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/model/AppOidcStandardSaveConfigParam.java new file mode 100644 index 00000000..d254d269 --- /dev/null +++ b/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/model/AppOidcStandardSaveConfigParam.java @@ -0,0 +1,151 @@ +/* + * eiam-application-oidc - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.oidc.model; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import cn.topiam.employee.common.enums.app.AuthorizationType; +import cn.topiam.employee.common.enums.app.InitLoginType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/7/10 01:45 + */ +@Data +@Schema(description = "保存 OIDC 应用配置参数") +public class AppOidcStandardSaveConfigParam implements Serializable { + + @Serial + private static final long serialVersionUID = 7257798528680745281L; + + /** + * 授权类型 + */ + @NotNull(message = "授权类型不能为空") + @Schema(description = "授权类型") + private List authGrantTypes; + + /** + * 授权类型 + */ + @NotNull(message = "授权类型不能为空") + @Schema(description = "授权类型") + private List redirectUris; + + /** + * SSO范围 + */ + @NotNull(message = "SSO范围不能为空") + @Schema(description = "SSO范围") + private AuthorizationType authorizationType; + + /** + * SSO发起方 + */ + @NotNull(message = "SSO发起方不能为空") + @Schema(description = "SSO发起方") + private InitLoginType initLoginType; + + /** + * SSO 发起登录URL + */ + @Schema(description = "SSO 发起登录URL") + private String initLoginUrl; + + /** + * 授予范围 + */ + @NotNull(message = "授予范围不能为空") + @Schema(description = "授予范围") + private List grantScopes; + + /** + * 客户端身份验证方法 + */ + @Schema(description = "客户端身份验证方法") + private List clientAuthMethods; + + /** + * 是否需要PKCE + */ + @NotNull(message = "请选择是否需要PKCE") + @Schema(description = "是否需要PKCE") + private Boolean requireProofKey; + + /** + * Access Token 生存时间 + */ + @NotBlank(message = "Access Token 生存时间不能为空") + @Schema(description = "Access Token 生存时间") + private String accessTokenTimeToLive; + + /** + * Access Token 格式 + */ + @Schema(description = "Access Token 格式") + private String accessTokenFormat; + + /** + * Refresh Token 生存时间 + */ + @NotBlank(message = "Refresh Token 生存时间不能为空") + @Schema(description = "Refresh Token 生存时间") + private String refreshTokenTimeToLive; + + /** + * Id Token 生存时间 + */ + @NotBlank(message = "Id Token 生存时间不能为空") + @Schema(description = "Id Token 生存时间") + private String idTokenTimeToLive; + + /** + * ID Token签名算法 + */ + @NotBlank(message = "ID Token签名算法不能为空") + @Schema(description = "ID Token签名算法") + private String idTokenSignatureAlgorithm; + + /** + * Token签名算法 + */ + @Schema(description = "令牌端点身份验证签名算法") + private String tokenEndpointAuthSigningAlgorithm; + + /** + * 重用刷新令牌 + */ + @Schema(description = "重用刷新令牌") + private Boolean reuseRefreshToken; + + /** + * 需要授权同意 + */ + @Schema(description = "需要授权同意") + private Boolean requireAuthConsent; + +} diff --git a/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/model/package-info.java b/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/model/package-info.java new file mode 100644 index 00000000..08520c0d --- /dev/null +++ b/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/model/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-application-oidc - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.oidc.model; \ No newline at end of file diff --git a/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/package-info.java b/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/package-info.java new file mode 100644 index 00000000..b9cb5118 --- /dev/null +++ b/eiam-application/eiam-application-oidc/src/main/java/cn/topiam/employee/application/oidc/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-application-oidc - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.oidc; \ No newline at end of file diff --git a/eiam-application/eiam-application-saml2/pom.xml b/eiam-application/eiam-application-saml2/pom.xml new file mode 100644 index 00000000..e08972cf --- /dev/null +++ b/eiam-application/eiam-application-saml2/pom.xml @@ -0,0 +1,41 @@ + + + + + cn.topiam + eiam-application + 1.0.0-beta1 + ../pom.xml + + jar + 4.0.0 + + eiam-application-saml2 + + + cn.topiam + eiam-application-core + ${project.version} + + + \ No newline at end of file diff --git a/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/package-info.java b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/package-info.java new file mode 100644 index 00000000..9890c7b6 --- /dev/null +++ b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-application-saml2 - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application; \ No newline at end of file diff --git a/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/AbstractSamlAppService.java b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/AbstractSamlAppService.java new file mode 100644 index 00000000..c74c9101 --- /dev/null +++ b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/AbstractSamlAppService.java @@ -0,0 +1,348 @@ +/* + * eiam-application-saml2 - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.saml2; + +import java.util.*; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringSubstitutor; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.AlternativeJdkIdGenerator; +import org.springframework.util.CollectionUtils; +import org.springframework.util.IdGenerator; + +import cn.topiam.employee.application.AbstractApplicationService; +import cn.topiam.employee.application.Saml2ApplicationService; +import cn.topiam.employee.application.exception.AppCertNotExistException; +import cn.topiam.employee.common.entity.account.UserEntity; +import cn.topiam.employee.common.entity.app.AppAccountEntity; +import cn.topiam.employee.common.entity.app.AppCertEntity; +import cn.topiam.employee.common.entity.app.AppSaml2ConfigEntity; +import cn.topiam.employee.common.entity.app.po.AppSaml2ConfigPO; +import cn.topiam.employee.common.enums.app.AppCertUsingType; +import cn.topiam.employee.common.enums.app.SamlAttributeStatementValueType; +import cn.topiam.employee.common.enums.app.SamlNameIdValueType; +import cn.topiam.employee.common.exception.app.AppAccountNotExistException; +import cn.topiam.employee.common.repository.account.UserRepository; +import cn.topiam.employee.common.repository.app.*; +import cn.topiam.employee.common.util.SamlKeyStoreProvider; +import cn.topiam.employee.core.protocol.Saml2ProtocolConfig; +import cn.topiam.employee.core.protocol.Saml2SsoModel; +import cn.topiam.employee.core.security.util.SecurityUtils; +import cn.topiam.employee.support.context.ApplicationContextHelp; +import static cn.topiam.employee.common.enums.app.SamlNameIdValueType.*; +import static cn.topiam.employee.core.security.util.UserUtils.getUser; + +/** + * SAML 应用配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/23 20:58 + */ +public abstract class AbstractSamlAppService extends AbstractApplicationService + implements Saml2ApplicationService { + + private static final Logger logger = LoggerFactory.getLogger(AbstractSamlAppService.class); + + /** + * 删除应用 + * + * @param appId {@link String} 应用ID + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(String appId) { + //删除应用 + appRepository.deleteById(Long.valueOf(appId)); + //删除证书 + appCertRepository.deleteByAppId(Long.valueOf(appId)); + //删除应用账户 + appAccountRepository.deleteAllByAppId(Long.valueOf(appId)); + //删除应用权限策略 + appAccessPolicyRepository.deleteAllByAppId(Long.valueOf(appId)); + //删除SAML2配置 + appSaml2ConfigRepository.deleteByAppId(Long.valueOf(appId)); + } + + /** + * 获取SAML2 协议配置 + * + * @param appId {@link String} + * @return {@link Saml2ProtocolConfig} + */ + @Override + public Saml2ProtocolConfig getProtocolConfig(String appId) { + //根据提供商ID查询配置 + AppSaml2ConfigPO saml2Config = appSaml2ConfigRepository.getByAppId(Long.valueOf(appId)); + Optional signCert = appCertRepository + .findByAppIdAndUsingType(Long.valueOf(appId), AppCertUsingType.SAML_SIGN); + Optional encryptCert = appCertRepository + .findByAppIdAndUsingType(Long.valueOf(appId), AppCertUsingType.SAML_ENCRYPT); + if (signCert.isEmpty()) { + logger.error("SAML2 应用证书不存在 [{}] ", appId); + throw new AppCertNotExistException(); + } + //构建配置 + Saml2ProtocolConfig config = getSamlAppConverter() + .appSaml2ConfigPoToSaml2ProtocolConfig(saml2Config); + //签名证书 + signCert + .ifPresent(appCertEntity -> config.setIdpSignCert(SamlKeyStoreProvider.getCredential( + StringUtils.defaultString(saml2Config.getSpEntityId(), + UUID.randomUUID().toString()), + appCertEntity.getPrivateKey(), appCertEntity.getCert(), ""))); + //加密证书存在 + encryptCert + .ifPresent(appCertEntity -> config.setIdpEncryptCert(SamlKeyStoreProvider.getCredential( + StringUtils.defaultString(saml2Config.getSpEntityId(), + UUID.randomUUID().toString()), + appCertEntity.getPrivateKey(), appCertEntity.getCert(), ""))); + return config; + } + + @Override + public Saml2SsoModel getSsoModel(String appId) { + //根据提供商ID查询配置 + Saml2ProtocolConfig saml2Config = getProtocolConfig(appId); + //构建配置 + return getSamlAppConverter().saml2ProtocolConfigToSaml2SsoModel(saml2Config); + } + + private SamlAppConverter getSamlAppConverter() { + return ApplicationContextHelp.getBean(SamlAppConverter.class); + } + + /** + * AppSaml2ConfigRepository + */ + protected final AppSaml2ConfigRepository appSaml2ConfigRepository; + + /** + * IdGenerator + */ + protected final IdGenerator idGenerator; + + protected AbstractSamlAppService(AppCertRepository appCertRepository, + AppAccountRepository appAccountRepository, + AppAccessPolicyRepository appAccessPolicyRepository, + AppRepository appRepository, + AppSaml2ConfigRepository appSaml2ConfigRepository) { + super(appCertRepository, appAccountRepository, appAccessPolicyRepository, appRepository); + this.appSaml2ConfigRepository = appSaml2ConfigRepository; + this.idGenerator = new AlternativeJdkIdGenerator(); + } + + @Mapper(componentModel = "spring") + interface SamlAppConverter { + + /** + * 应用 Saml 2 配置实体到 Saml 2 协议配置 + * + * @param entity {@link AppSaml2ConfigEntity} + * @return {@link Saml2ProtocolConfig} + */ + @Mapping(target = "idpSignCert", ignore = true) + @Mapping(target = "idpEncryptCert", ignore = true) + Saml2ProtocolConfig appSaml2ConfigPoToSaml2ProtocolConfig(AppSaml2ConfigPO entity); + + /** + * 应用 Saml 2 配置实体到 Saml 2 协议配置 + * + * @param config {@link Saml2ProtocolConfig} + * @return {@link Saml2SsoModel} + */ + default Saml2SsoModel saml2ProtocolConfigToSaml2SsoModel(Saml2ProtocolConfig config) { + if (config == null) { + return null; + } + Saml2SsoModel.Saml2SsoModelBuilder saml2SsoModel = Saml2SsoModel.builder(); + saml2SsoModel.appId(config.getAppId()); + saml2SsoModel.appCode(config.getAppCode()); + saml2SsoModel.spEntityId(config.getSpEntityId()); + saml2SsoModel.spAcsUrl(config.getSpAcsUrl()); + saml2SsoModel.recipient(config.getRecipient()); + //Audience为空默认使用 SP EntityId + saml2SsoModel + .audience(StringUtils.defaultString(config.getAudience(), config.getSpEntityId())); + saml2SsoModel.spSlsUrl(config.getSpSloUrl()); + saml2SsoModel.spRequestsSigned(config.getSpRequestsSigned()); + saml2SsoModel.spSignCert(config.getSpSignCert()); + saml2SsoModel.acsBinding(config.getAcsBinding()); + saml2SsoModel.slsBinding(config.getSlsBinding()); + saml2SsoModel.nameIdFormat(config.getNameIdFormat()); + //NameID 值 + saml2SsoModel + .nameIdValue(getNameIdValue(config.getAppId(), config.getNameIdValueType())); + //断言签名相关 + saml2SsoModel.assertSigned(config.getAssertSigned()); + saml2SsoModel.assertSignAlgorithm(config.getAssertSignAlgorithm()); + //断言签名相关 + saml2SsoModel.assertEncrypted(config.getAssertEncrypted()); + saml2SsoModel.assertEncryptAlgorithm(config.getAssertEncryptAlgorithm()); + //响应签名相关 + saml2SsoModel.responseSigned(config.getResponseSigned()); + saml2SsoModel.responseSignAlgorithm(config.getResponseSignAlgorithm()); + saml2SsoModel.authnContextClassRef(config.getAuthnContextClassRef()); + + saml2SsoModel.relayState(config.getRelayState()); + //加密、签名相关证书 + saml2SsoModel.idpSignCert(config.getIdpSignCert()); + saml2SsoModel.idpEncryptCert(config.getIdpEncryptCert()); + //处理属性 + saml2SsoModel.attributeStatements( + getAttributeStatementList(config.getAppId(), config.getAttributeStatements())); + return saml2SsoModel.build(); + } + + /** + * 获取NameId value + * + * @param appId {@link String} 应用ID + * @param nameIdValueType {@link SamlNameIdValueType} + * @return {@link String} + */ + private String getNameIdValue(String appId, SamlNameIdValueType nameIdValueType) { + UserEntity user = getUser(); + //@formatter:off + //用户名 + if (USER_USERNAME.equals(nameIdValueType)) { + return user.getUsername(); + } + //姓名 + if (USER_FULL_NAME.equals(nameIdValueType)) { + return user.getFullName(); + } + //用户名 + if (USER_NICK_NAME.equals(nameIdValueType)) { + return user.getNickName(); + } + //邮箱 + if (USER_EMAIL.equals(nameIdValueType)) { + return user.getEmail(); + } + //应用用户名 + Long userId = Long.valueOf(SecurityUtils.getCurrentUserId()); + Optional optional = getAppAccountRepository().findByAppIdAndUserId(Long.valueOf(appId), userId); + if (optional.isEmpty()) { + logger.error("用户: " + SecurityUtils.getCurrentUserName() + "应用账户不存在"); + throw new AppAccountNotExistException(); + } + return optional.get().getAccount(); + //@formatter:on + } + + /** + * 获取属性List + * + * @param appId {@link String} 应用ID + * @param attributeStatements {@link List} + * @return {@link Saml2SsoModel.AttributeStatement} + */ + private List getAttributeStatementList(String appId, + List attributeStatements) { + if (!CollectionUtils.isEmpty(attributeStatements)) { + List list = new ArrayList<>(); + UserEntity user = getUser(); + //@formatter:off + //封装变量 + for (AppSaml2ConfigEntity.AttributeStatement attributeStatement : attributeStatements) { + Saml2SsoModel.AttributeStatement attributeStatementModal = new Saml2SsoModel.AttributeStatement(); + attributeStatementModal.setKey(attributeStatement.getName()); + attributeStatementModal.setNameFormat(attributeStatement.getNameFormat()); + //用户名 + if (attributeStatement.getValueExpression().contains(SamlAttributeStatementValueType.USERNAME.getExpression())){ + HashMap values = new HashMap<>(16); + values.put(SamlAttributeStatementValueType.USERNAME.getCode(), user.getUsername()); + attributeStatementModal.setValue(new StringSubstitutor(values).replace(attributeStatement.getValueExpression())); + list.add(attributeStatementModal); + } + //昵称 + else if (attributeStatement.getValueExpression().contains(SamlAttributeStatementValueType.NICK_NAME.getExpression())){ + if (StringUtils.isNotBlank(user.getNickName())) { + HashMap values = new HashMap<>(16); + values.put(SamlAttributeStatementValueType.NICK_NAME.getCode(), user.getNickName()); + attributeStatementModal.setValue(new StringSubstitutor(values).replace(attributeStatement.getValueExpression())); + list.add(attributeStatementModal); + } + } + //手机号 + else if (attributeStatement.getValueExpression().contains(SamlAttributeStatementValueType.PHONE.getExpression())){ + if (StringUtils.isNotBlank(user.getPhone())) { + HashMap values = new HashMap<>(16); + values.put(SamlAttributeStatementValueType.PHONE.getCode(), user.getPhone()); + attributeStatementModal.setValue(new StringSubstitutor(values).replace(attributeStatement.getValueExpression())); + list.add(attributeStatementModal); + } + } + //邮箱 + else if (attributeStatement.getValueExpression().contains(SamlAttributeStatementValueType.EMAIL.getExpression())){ + if (StringUtils.isNotBlank(user.getEmail())){ + HashMap values = new HashMap<>(16); + values.put(SamlAttributeStatementValueType.EMAIL.getCode(), user.getEmail()); + attributeStatementModal.setValue(new StringSubstitutor(values).replace(attributeStatement.getValueExpression())); + list.add(attributeStatementModal); + } + } + //应用用户 + else if (attributeStatement.getValueExpression().contains(SamlAttributeStatementValueType.APP_USERNAME.getExpression())){ + //应用用户名 + Long userId = Long.valueOf(SecurityUtils.getCurrentUserId()); + Optional optional = getAppAccountRepository().findByAppIdAndUserId(Long.valueOf(appId), userId); + if (optional.isEmpty()) { + logger.error("用户 [{}] 应用账户不存在",SecurityUtils.getCurrentUserName() ); + throw new AppAccountNotExistException(); + } + HashMap values = new HashMap<>(16); + values.put(SamlAttributeStatementValueType.APP_USERNAME.getCode(), optional.get().getAccount()); + attributeStatementModal.setValue(new StringSubstitutor(values).replace(attributeStatement.getValueExpression())); + list.add(attributeStatementModal); + } else { + attributeStatementModal.setValue(attributeStatement.getValueExpression()); + list.add(attributeStatementModal); + } + } + //@formatter:on + return list; + } + return new ArrayList<>(); + } + + /** + * 获取 AppAccountRepository + * + * @return {@link AppAccountRepository} + */ + private AppAccountRepository getAppAccountRepository() { + return ApplicationContextHelp.getBean(AppAccountRepository.class); + } + + /** + * 获取 UserRepository + * + * @return {@link UserRepository} + */ + private UserRepository getUserRepository() { + return ApplicationContextHelp.getBean(UserRepository.class); + } + + } +} diff --git a/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/Saml2StandardApplicationServiceImpl.java b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/Saml2StandardApplicationServiceImpl.java new file mode 100644 index 00000000..6d771996 --- /dev/null +++ b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/Saml2StandardApplicationServiceImpl.java @@ -0,0 +1,256 @@ +/* + * eiam-application-saml2 - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.saml2; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import javax.validation.ConstraintViolationException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import cn.topiam.employee.application.exception.AppNotExistException; +import cn.topiam.employee.application.saml2.converter.AppSaml2StandardConfigConverter; +import cn.topiam.employee.application.saml2.model.AppSaml2StandardSaveConfigParam; +import cn.topiam.employee.audit.context.AuditContext; +import cn.topiam.employee.common.entity.app.AppEntity; +import cn.topiam.employee.common.entity.app.AppSaml2ConfigEntity; +import cn.topiam.employee.common.entity.app.po.AppSaml2ConfigPO; +import cn.topiam.employee.common.enums.app.*; +import cn.topiam.employee.common.repository.app.*; +import cn.topiam.employee.support.exception.TopIamException; +import cn.topiam.employee.support.util.BeanUtils; +import cn.topiam.employee.support.validation.ValidationHelp; +import static org.opensaml.saml.common.xml.SAMLConstants.SAML2_POST_BINDING_URI; + +import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; + +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_BY; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_TIME; + +/** + * SAML2 用户应用 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/20 23:20 + */ +@SuppressWarnings({ "rawtypes", "DuplicatedCode" }) +@Component +public class Saml2StandardApplicationServiceImpl extends AbstractSamlAppService { + private final Logger logger = LoggerFactory + .getLogger(Saml2StandardApplicationServiceImpl.class); + + /** + * 更新应用配置 + * + * @param appId {@link String} + * @param config {@link Map} + */ + @Override + public void saveConfig(String appId, Map config) { + AppSaml2StandardSaveConfigParam model; + try { + ObjectMapper mapper = new ObjectMapper(); + String value = mapper.writeValueAsString(config); + // 指定序列化输入的类型 + mapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false); + model = mapper.readValue(value, AppSaml2StandardSaveConfigParam.class); + } catch (Exception e) { + throw new TopIamException(e.getMessage()); + } + //@formatter:off + ValidationHelp.ValidationResult validationResult = ValidationHelp.validateEntity(model); + if (validationResult.isHasErrors()) { + throw new ConstraintViolationException(validationResult.getConstraintViolations()); + } + //@formatter:on + //1、修改基本信息 + Optional optional = appRepository.findById(Long.valueOf(appId)); + if (optional.isEmpty()) { + AuditContext.setContent("保存配置失败,应用 [" + appId + "] 不存在!"); + logger.error(AuditContext.getContent()); + throw new AppNotExistException(); + } + AppEntity appEntity = optional.get(); + appEntity.setAuthorizationType(model.getAuthorizationType()); + appEntity.setInitLoginUrl(model.getInitLoginUrl()); + appEntity.setInitLoginType(model.getInitLoginType()); + appRepository.save(appEntity); + //2、修改SAML2配置 + Optional saml = appSaml2ConfigRepository + .findByAppId(Long.valueOf(appId)); + if (saml.isEmpty()) { + AuditContext.setContent("保存配置失败,应用 [" + appId + "] 不存在!"); + logger.error(AuditContext.getContent()); + throw new AppNotExistException(); + } + AppSaml2ConfigEntity entity = saml.get(); + AppSaml2ConfigEntity saml2Config = appSaml2StandardConfigConverter + .saveSaml2ConfigConverterToEntity(model); + BeanUtils.merge(saml2Config, entity, LAST_MODIFIED_BY, LAST_MODIFIED_TIME); + appSaml2ConfigRepository.save(entity); + } + + /** + * 获取配置 + * + * @param appId {@link String} + * @return {@link Map} + */ + @Override + public Object getConfig(String appId) { + AppSaml2ConfigPO po = appSaml2ConfigRepository.getByAppId(Long.valueOf(appId)); + return appSaml2StandardConfigConverter.entityConverterToSaml2ConfigResult(po); + } + + /** + * 获取应用标志 + * + * @return {@link String} + */ + @Override + public String getCode() { + return "saml2"; + } + + /** + * 获取应用名称 + * + * @return {@link String} + */ + @Override + public String getName() { + return "SAML2"; + } + + /** + * 获取应用描述 + * + * @return {@link String} + */ + @Override + public String getDescription() { + return "SAML(Security Assertion Markup Language,安全断言标记语言,版本 2.0)基于 XML 协议,使用包含断言(Assertion)的安全令牌,在授权方(TopIAM)和消费方(应用)之间传递身份信息,实现基于网络跨域的单点登录。SAML 协议是成熟的认证协议,在国内外的公有云和私有云中有非常广泛的运用。"; + } + + /** + * 获取应用类型 + * + * @return {@link AppType} + */ + @Override + public AppType getType() { + return AppType.STANDARD; + } + + /** + * 获取应用协议 + * + * @return {@link AppProtocol} + */ + @Override + public AppProtocol getProtocol() { + return AppProtocol.SAML2; + } + + /** + * 获取表单Schema + * + * @return {@link Map} + */ + @Override + public List getFormSchema() { + return new ArrayList<>(); + } + + /** + * 获取base64图标 + * + * @return {@link String} + */ + @Override + public String getBase64Icon() { + return "data:image/svg+xml;base64,PHN2ZyB0PSIxNjYwOTc4ODM3NTM4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjIyMzIiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNTc3LjgwODQxIDE4Ni4yOTkwNzdjLTU3LjA2NTAyNiAyNy42OTM5NDktMTU0LjMzODQ2MiAyNDAuNjMzNDM2LTE5OS41NDIxNTQgMzQ0LjU5MjQxIDI4LjgzOTM4NS02My44Njg3MTggMTQ4LjYxNDU2NC0yMTIuNDI3NDg3IDIwNi4wOTk2OTMtMjAzLjUyIDk2Ljk4Nzg5NyAyLjg0ODgyMSAyNDQuMTQxOTQ5IDI0OS42MDY1NjQgMzAzLjM3OTY5MiAzNzMuODA5MjMxLTM2LjYwOC04MS40ODY3NjktNzQuMzU0ODcyLTE4OC4yMDU5NDktMTM1LjExNTQ4Ny0yODAuNzUzMjMxLTYwLjc2Mzg5Ny05Mi41NDQtMTQzLjk0MDkyMy0yMTUuODYwNTEzLTE3NC44MjE3NDQtMjM0LjEyODQxeiIgZmlsbD0iI0NCMzkzOSIgcC1pZD0iMjIzMyI+PC9wYXRoPjxwYXRoIGQ9Ik04MjUuNDg4NDEgODMxLjgwMzA3N2M2LjAxOTI4Mi02My4xNDMzODUtMTI1LjI3OTE3OS0yNTYuOTY4MjA1LTE5MC41NjU3NDMtMzQ5LjYzNjkyMyAzOS41NTIgNTcuODQ2MTU0IDEwNC4xNTkxNzkgMjM3LjQwNzE3OSA2Ni42NTg0NjEgMjgxLjg3NTY5Mi01Mi44NzM4NDYgODEuMzYyMDUxLTM0MC4xNjQ5MjMgNzguNzI5ODQ2LTQ3Ny4wNTYgNjQuNzQxNzQ0IDg4LjYzODM1OSAxMS4xMDY0NjIgMTk5LjQyNCAzNC4zNTk3OTUgMzEwLjA2ODUxMyAzMC41ODg3MTggMTEwLjY0NDUxMy0zLjc3NDM1OSAyNTkuMjMyODIxLTEwLjY5MjkyMyAyOTAuODk0NzY5LTI3LjU2OTIzMXoiIGZpbGw9IiNDQjM5MzkiIHAtaWQ9IjIyMzQiPjwvcGF0aD48cGF0aCBkPSJNMTM3Ljg0NjE1NCA2OTYuMTkyYzUyLjg3NzEyOCAzNS4wMzU4OTcgMjg1LjY4OTQzNiAxMC40MjA1MTMgMzk4LjE4MTc0My0zLjU2NDMwOC02OS42NTQ5NzQgNy42NzAxNTQtMjU4LjQ5NDM1OS0xOS44NDY1NjQtMjgwLjA3Mzg0Ni03My44NjI1NjRDMjA5LjA1MzUzOCA1MzMuODE5MDc3IDM0Ni42MDEwMjYgMjgxLjU4MDMwOCA0MjMuMzc4MDUxIDE2Ny4zODQ2MTUgMzcxLjg1NjQxIDI0MC4zNjQzMDggMjk5LjE5MTc5NSAzMjcuMTYxNDM2IDI1MC40NDM0ODcgNDI2LjU2MTY0MSAyMDEuNjkxODk3IDUyNS45NjE4NDYgMTM3Ljg1OTI4MiA2NjAuMzE1ODk3IDEzNy44NDYxNTQgNjk2LjE5MnoiIGZpbGw9IiNDQjM5MzkiIHAtaWQ9IjIyMzUiPjwvcGF0aD48L3N2Zz4="; + } + + /** + * 创建应用 + * + * @param name {@link String} 名称 + * @param remark {@link String} 备注 + */ + @Override + public String create(String name, String remark) { + //1、创建基础信息 + AppEntity appEntity = new AppEntity(); + appEntity.setName(name); + appEntity.setCode( + org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric(32).toLowerCase()); + appEntity.setTemplate(getCode()); + appEntity.setType(AppType.STANDARD); + appEntity.setEnabled(true); + appEntity.setProtocol(getProtocol()); + appEntity.setClientId(idGenerator.generateId().toString().replace("-", "")); + appEntity.setClientSecret(idGenerator.generateId().toString().replace("-", "")); + appEntity.setInitLoginType(InitLoginType.PORTAL_OR_APP); + appEntity.setAuthorizationType(AuthorizationType.AUTHORIZATION); + appEntity.setRemark(remark); + appRepository.save(appEntity); + //2、创建证书 + createCertificate(appEntity.getId(), appEntity.getCode(), AppCertUsingType.SAML_SIGN); + createCertificate(appEntity.getId(), appEntity.getCode(), AppCertUsingType.SAML_ENCRYPT); + //3、创建配置 + AppSaml2ConfigEntity entity = new AppSaml2ConfigEntity(); + entity.setAppId(appEntity.getId()); + //Binding POST + entity.setAcsBinding(SAML2_POST_BINDING_URI); + //NameID + entity.setNameIdFormat(SamlNameIdFormatType.PERSISTENT); + //应用账户名 + entity.setNameIdValueType(SamlNameIdValueType.APP_USERNAME); + //签名非对称算法 + entity.setAssertSigned(true); + entity.setAssertSignAlgorithm(SamlSignAssertAlgorithmType.RSA_SHA256); + //加密使用的非对称算法 + entity.setAssertEncrypted(false); + entity.setAssertEncryptAlgorithm(SamlEncryptAssertAlgorithmType.RSA_SHA256); + //SAML 身份认证上下文 + entity.setAuthnContextClassRef(AuthnContextClassRefType.UNSPECIFIED_AUTHN_CTX); + appSaml2ConfigRepository.save(entity); + return appEntity.getId().toString(); + } + + private final AppSaml2StandardConfigConverter appSaml2StandardConfigConverter; + + public Saml2StandardApplicationServiceImpl(AppRepository appRepository, + AppCertRepository appCertRepository, + AppAccountRepository appAccountRepository, + AppAccessPolicyRepository appAccessPolicyRepository, + AppSaml2ConfigRepository appSaml2ConfigRepository, + AppSaml2StandardConfigConverter appSaml2StandardConfigConverter) { + super(appCertRepository, appAccountRepository, appAccessPolicyRepository, appRepository, + appSaml2ConfigRepository); + this.appSaml2StandardConfigConverter = appSaml2StandardConfigConverter; + } +} diff --git a/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/converter/AppSaml2StandardConfigConverter.java b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/converter/AppSaml2StandardConfigConverter.java new file mode 100644 index 00000000..e4104c79 --- /dev/null +++ b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/converter/AppSaml2StandardConfigConverter.java @@ -0,0 +1,117 @@ +/* + * eiam-application-saml2 - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.saml2.converter; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import cn.topiam.employee.application.saml2.model.AppSaml2StandardConfigGetResult; +import cn.topiam.employee.application.saml2.model.AppSaml2StandardSaveConfigParam; +import cn.topiam.employee.application.saml2.model.Saml2ConverterUtils; +import cn.topiam.employee.common.entity.app.AppSaml2ConfigEntity; +import cn.topiam.employee.common.entity.app.po.AppSaml2ConfigPO; + +/** + * 应用映射 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/14 22:45 + */ +@Mapper(componentModel = "spring") +public interface AppSaml2StandardConfigConverter { + /** + * 实体转Saml2配置结果 + * + * @param config {@link AppSaml2ConfigPO} + * @return {@link AppSaml2StandardConfigGetResult} + */ + default AppSaml2StandardConfigGetResult entityConverterToSaml2ConfigResult(AppSaml2ConfigPO config) { + if (config == null) { + return null; + } + AppSaml2StandardConfigGetResult result = new AppSaml2StandardConfigGetResult(); + //协议端点域 + result.setProtocolEndpoint( + Saml2ConverterUtils.getProtocolEndpointDomain(config.getAppCode().toString())); + //SpEntityId + result.setSpEntityId(config.getSpEntityId()); + //SP 单点登录 ACS URL + result.setSpAcsUrl(config.getSpAcsUrl()); + //单点登录 ACS BINDING + result.setAcsBinding(config.getAcsBinding()); + //是否对 SAML Request 签名进行验证 ,用来对SAML Request签名进行验证,对应SP元数据文件中“AuthnRequestsSigned”值 + result.setSpRequestsSigned(config.getSpRequestsSigned()); + //SP公钥证书,用来验证SAML request的签名,对应SP元数据文件中 use='signing' 证书内容 + result.setSpSignCert(config.getSpSignCert()); + //Audience + result.setSpAudience(config.getAudience()); + //NameId 值类型 + result.setNameIdValueType(config.getNameIdValueType()); + //SAML Response 中指定账户标识 NameID 字段格式。 + result.setNameIdFormat(config.getNameIdFormat()); + //是否对断言签名 + result.setAssertSigned(config.getAssertSigned()); + //断言签名使用的非对称算法 + result.setAssertSignAlgorithm(config.getAssertSignAlgorithm()); + //是否对断言加密 + result.setAssertEncrypted(config.getAssertEncrypted()); + //断言加密使用的非对称算法 + result.setAssertEncryptAlgorithm(config.getAssertEncryptAlgorithm()); + //是否对响应加密 + result.setResponseSigned(config.getResponseSigned()); + //响应加密使用的非对称算法 + result.setResponseSignAlgorithm(config.getResponseSignAlgorithm()); + //SAML 身份认证上下文 + result.setAuthnContextClassRef(config.getAuthnContextClassRef()); + //IDP 发起 SSO 登录成功后,应用应自动跳转的地址。在 SAML Response 中会在 RelayState 参数中传递,应用读取后实现跳转。 + result.setRelayState(config.getRelayState()); + //SSO 发起方 + result.setInitLoginType(config.getInitLoginType()); + //登录发起地址 + result.setInitLoginUrl(config.getInitLoginUrl()); + //授权类型 + result.setAuthorizationType(config.getAuthorizationType()); + //属性声明 + List list = config.getAttributeStatements(); + if (list != null) { + result.setAttributeStatements(list); + } + result.setAdditionalConfig(config.getAdditionalConfig()); + return result; + } + + /** + * 将 Saml 2 配置转换器保存到实体 + * + * @param param {@link AppSaml2StandardSaveConfigParam} + * @return {@link AppSaml2ConfigEntity} + */ + @Mapping(target = "slsBinding", ignore = true) + @Mapping(target = "recipient", ignore = true) + @Mapping(target = "appId", ignore = true) + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "spMetadata", ignore = true) + @Mapping(target = "remark", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + AppSaml2ConfigEntity saveSaml2ConfigConverterToEntity(AppSaml2StandardSaveConfigParam param); +} diff --git a/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/AppSaml2StandardConfigGetResult.java b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/AppSaml2StandardConfigGetResult.java new file mode 100644 index 00000000..0b014777 --- /dev/null +++ b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/AppSaml2StandardConfigGetResult.java @@ -0,0 +1,170 @@ +/* + * eiam-application-saml2 - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.saml2.model; + +import java.util.List; +import java.util.Map; + +import cn.topiam.employee.common.entity.app.AppSaml2ConfigEntity; +import cn.topiam.employee.common.enums.app.*; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * Saml2 配置返回 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/31 22:46 + */ +@Data +@Schema(description = "Saml2 配置返回结果") +public class AppSaml2StandardConfigGetResult { + /** + * SSO 发起方 + */ + @Parameter(description = "SSO 发起方") + private InitLoginType initLoginType; + + /** + * SSO 登录链接 + */ + @Parameter(description = "SSO 登录链接") + private String initLoginUrl; + + /** + * 授权范围 + */ + @Parameter(description = "SSO 授权范围") + private AuthorizationType authorizationType; + /** + * SpEntityId + */ + @Parameter(description = "SP Entity ID") + private String spEntityId; + + /** + * SP 单点登录 ACS URL + */ + @Parameter(description = "SP 单点登录 ACS URL") + private String spAcsUrl; + + /** + * 允许使用SAML断言的资源,默认和SP Entity ID相同。 + */ + @Parameter(description = "Audience") + private String spAudience; + + /** + * 是否对 SAML Request 签名进行验证 ,用来对SAML Request签名进行验证,对应SP元数据文件中“AuthnRequestsSigned”值 + */ + @Parameter(description = "是否对 SAML Request 签名进行验证") + private Boolean spRequestsSigned; + + /** + * SP公钥证书,用来验证SAML request的签名,对应SP元数据文件中 use='signing' 证书内容 + */ + @Parameter(description = "SP公钥证书,用来验证SAML request的签名request的签名") + private String spSignCert; + + /** + * 单点登录 ACS BINDING + */ + @Parameter(name = "单点登录 ACS BINDING") + private String acsBinding; + + /** + * NameId 值类型 + */ + @Parameter(description = "NameIdType") + private SamlNameIdValueType nameIdValueType; + + /** + * SAML Response 中指定账户标识 NameID 字段格式。一般无需修改。 + */ + @Parameter(description = "NameIdFormat") + private SamlNameIdFormatType nameIdFormat; + + /** + * 是否对断言签名 + */ + @Parameter(description = "是否对断言签名") + private Boolean assertSigned; + + /** + * 断言签名使用的非对称算法 + */ + @Parameter(description = "断言签名使用的非对称算法") + private SamlSignAssertAlgorithmType assertSignAlgorithm; + + /** + * 是否对断言加密 + */ + @Parameter(description = "是否对断言加密") + private Boolean assertEncrypted; + + /** + * 加密使用的非对称算法 + */ + @Parameter(description = "加密使用的非对称算法") + private SamlEncryptAssertAlgorithmType assertEncryptAlgorithm; + + /** + * 响应是否加密 + */ + @Parameter(description = "响应是否加密") + private Boolean responseSigned; + + /** + * 响应签名使用的非对称算法 + */ + @Parameter(description = "响应签名使用的非对称算法") + private SamlSignAssertAlgorithmType responseSignAlgorithm; + + /** + * SAML 身份认证上下文 + */ + @Parameter(description = "AuthnContextClassRef") + private AuthnContextClassRefType authnContextClassRef; + + /** + * IDP 发起 SSO 登录成功后,应用应自动跳转的地址。在 SAML Response 中会在 RelayState 参数中传递,应用读取后实现跳转。 + */ + @Parameter(description = "RelayState") + private String relayState; + + /** + * 在 SAML Response 中,可以将额外用户字段(例如邮箱、显示名等)返回给应用解析。 + */ + @Parameter(description = "AttributeStatements") + private List attributeStatements; + + /** + * 协议端点域 + */ + @Parameter(description = "协议端点域") + private Saml2ProtocolEndpoint protocolEndpoint; + + /** + * 模版配置 + */ + private Map additionalConfig; + +} diff --git a/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/AppSaml2StandardSaveConfigParam.java b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/AppSaml2StandardSaveConfigParam.java new file mode 100644 index 00000000..ea549a35 --- /dev/null +++ b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/AppSaml2StandardSaveConfigParam.java @@ -0,0 +1,175 @@ +/* + * eiam-application-saml2 - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.saml2.model; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import cn.topiam.employee.common.entity.app.AppSaml2ConfigEntity; +import cn.topiam.employee.common.enums.app.*; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/7/10 01:45 + */ +@Data +@Schema(description = "保存 SAML2 应用配置参数") +public class AppSaml2StandardSaveConfigParam implements Serializable { + + @Serial + private static final long serialVersionUID = 7257798528680745281L; + + /** + * 应用ID + */ + @Schema(description = "授权类型") + private AuthorizationType authorizationType; + + /** + * SSO 发起登录类型 + */ + @Schema(description = "SSO 发起登录类型") + private InitLoginType initLoginType; + + /** + * SSO 发起登录URL + */ + @Schema(description = "SSO 发起登录URL") + private String initLoginUrl; + + /** + * ACS URL + */ + @Schema(description = "ACS URL") + private String spAcsUrl; + + /** + * SLO URL + */ + @Schema(description = "SLO URL") + private String spSloUrl; + + /** + * SP Entity ID + */ + @Schema(description = "SP Entity ID") + private String spEntityId; + + /** + * Name ID 类型 + */ + @Schema(description = "Name ID 类型") + private SamlNameIdValueType nameIdValueType; + + /** + * RelayState + */ + @Schema(description = "RelayState") + private String relayState; + + /** + * Audience + */ + @Schema(description = "Audience") + private String audience; + + /** + * NameID 格式 + */ + @Schema(description = "NameID 格式") + private SamlNameIdFormatType nameIdFormat; + + /** + * Acs Binding 格式 + */ + @Schema(description = "ACS Binding 格式") + private String acsBinding; + + /** + * 是否签名断言 + */ + @Schema(description = "是否签名断言") + private Boolean assertSigned; + + /** + * 是否签名断言 + */ + @Schema(description = "签名断言算法") + private SamlSignAssertAlgorithmType assertSignAlgorithm; + + /** + * 是否加密断言 + */ + @Schema(description = "是否加密断言") + private Boolean assertEncrypted; + + /** + * 断言签名算法 + */ + @Schema(description = "加密断言算法") + private SamlEncryptAssertAlgorithmType assertEncryptAlgorithm; + + /** + * 是否签名断言 + */ + @Schema(description = "身份认证上下文") + private AuthnContextClassRefType authnContextClassRef; + + /** + * 响应是否加密 + */ + @Parameter(description = "响应是否加密") + private Boolean responseSigned; + + /** + * 响应签名使用的非对称算法 + */ + @Parameter(description = "响应签名使用的非对称算法") + private SamlSignAssertAlgorithmType responseSignAlgorithm; + + /** + * 在 SAML Response 中,可以将额外用户字段(例如邮箱、显示名等)返回给应用解析。 + */ + @Schema(description = "Attribute Statements") + private List attributeStatements; + + /** + * 是否验证请求签名 + */ + @Schema(description = "验证请求签名") + private Boolean spRequestsSigned; + + /** + * 请求验证证书 + */ + @Schema(description = "请求验证证书") + private String spSignCert; + + /** + * 模版配置 + */ + @Schema(description = "模版配置") + private Map additionalConfig; +} diff --git a/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/Saml2ConverterUtils.java b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/Saml2ConverterUtils.java new file mode 100644 index 00000000..5c20e71b --- /dev/null +++ b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/Saml2ConverterUtils.java @@ -0,0 +1,54 @@ +/* + * eiam-application-saml2 - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.saml2.model; + +import cn.topiam.employee.core.context.ServerContextHelp; +import static cn.topiam.employee.common.constants.ProtocolConstants.*; + +/** + * Saml2ConverterUtils + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/23 22:57 + */ +public class Saml2ConverterUtils { + /** + * 应用ID + * + * @param appCode {@link String} + * @return {@link Saml2ProtocolEndpoint} + */ + public static Saml2ProtocolEndpoint getProtocolEndpointDomain(String appCode) { + Saml2ProtocolEndpoint domain = new Saml2ProtocolEndpoint(); + //IDP + String baseUrl = ServerContextHelp.getPortalPublicBaseUrl(); + //元数据端点 + domain.setIdpMetaEndpoint(baseUrl + Saml2EndpointConstants.SAML_METADATA_PATH + .replace(APP_CODE_VARIABLE, appCode)); + //EntityId端点 + domain.setIdpEntityIdEndpoint(baseUrl + Saml2EndpointConstants.SAML_METADATA_PATH + .replace(APP_CODE_VARIABLE, appCode)); + //Sso端点 + domain.setIdpSsoEndpoint( + baseUrl + Saml2EndpointConstants.SAML_SSO_PATH.replace(APP_CODE_VARIABLE, appCode)); + //Slo端点 + domain.setIdpSloEndpoint( + baseUrl + Saml2EndpointConstants.SAML_LOGOUT_PATH.replace(APP_CODE_VARIABLE, appCode)); + return domain; + } +} diff --git a/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/Saml2ProtocolEndpoint.java b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/Saml2ProtocolEndpoint.java new file mode 100644 index 00000000..448ba76d --- /dev/null +++ b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/Saml2ProtocolEndpoint.java @@ -0,0 +1,64 @@ +/* + * eiam-application-saml2 - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.saml2.model; + +import java.io.Serial; +import java.io.Serializable; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 协议端点域 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/4 23:37 + */ +@Data +@Schema(description = "协议端点") +public class Saml2ProtocolEndpoint implements Serializable { + + @Serial + private static final long serialVersionUID = -2261602995152894964L; + + /** + * IDP 元数据端点 + */ + @Parameter(description = "IDP 元数据端点") + private String idpMetaEndpoint; + + /** + * IDP EntityId 端点 + */ + @Parameter(description = "IDP EntityId 端点") + private String idpEntityIdEndpoint; + + /** + * IDP SSO 端点 + */ + @Parameter(description = "IDP SSO 端点") + private String idpSsoEndpoint; + + /** + * IDP SLO 端点 + */ + @Parameter(description = "IDP SLO 端点") + private String idpSloEndpoint; +} diff --git a/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/package-info.java b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/package-info.java new file mode 100644 index 00000000..e3e82bc3 --- /dev/null +++ b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/model/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-application-saml2 - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.saml2.model; \ No newline at end of file diff --git a/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/package-info.java b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/package-info.java new file mode 100644 index 00000000..48f19763 --- /dev/null +++ b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-application-saml2 - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.application.saml2; \ No newline at end of file diff --git a/eiam-application/pom.xml b/eiam-application/pom.xml new file mode 100644 index 00000000..450c5e16 --- /dev/null +++ b/eiam-application/pom.xml @@ -0,0 +1,66 @@ + + + + + cn.topiam + eiam + 1.0.0-beta1 + ../pom.xml + + pom + 4.0.0 + + eiam-application + + + eiam-application-core + eiam-application-saml2 + eiam-application-oidc + eiam-application-cas + eiam-application-jwt + eiam-application-form + eiam-application-all + + + + + cn.topiam + eiam-audit + ${project.version} + + + + + + src/main/java + + ** + + + **/*.java + + false + + + + \ No newline at end of file diff --git a/eiam-audit/pom.xml b/eiam-audit/pom.xml new file mode 100644 index 00000000..8788ec77 --- /dev/null +++ b/eiam-audit/pom.xml @@ -0,0 +1,42 @@ + + + + + cn.topiam + eiam + 1.0.0-beta1 + ../pom.xml + + jar + 4.0.0 + + eiam-audit + + + + cn.topiam + eiam-core + ${project.version} + + + \ No newline at end of file diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/Audit.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/Audit.java new file mode 100644 index 00000000..e5c076b1 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/Audit.java @@ -0,0 +1,49 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.annotation; + +import java.lang.annotation.*; + +import cn.topiam.employee.audit.enums.EventType; + +/** + * Audit + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/28 21:56 + */ +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface Audit { + + /** + * 类型 + * + * @return {@link Class} + */ + EventType type(); + + /** + * 审计内容 支持SPEL表达式 + * + * @return {@link String} + */ + String content() default ""; +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/AuditAspect.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/AuditAspect.java new file mode 100644 index 00000000..d258aba4 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/AuditAspect.java @@ -0,0 +1,177 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.annotation; + +import java.util.Map; +import java.util.Objects; + +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.stereotype.Component; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONWriter; +import com.beust.jcommander.internal.Maps; + +import cn.topiam.employee.audit.context.AuditContext; +import cn.topiam.employee.audit.enums.EventStatus; +import cn.topiam.employee.audit.event.AuditEventPublish; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; +import static cn.topiam.employee.support.constant.EiamConstants.COLON; + +/** + * 审计切面 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/28 19:20 + */ +@Component +@Aspect +@AllArgsConstructor +public class AuditAspect { + + private final Logger logger = LoggerFactory + .getLogger(AuditAspect.class); + /** + * SpelExpressionParser + */ + private final SpelExpressionParser spelExpressionParser = new SpelExpressionParser(); + /** + * 参数名发现器 + */ + private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); + + private final ApplicationContext applicationContext; + + private final AuditEventPublish auditEventPublish; + + private static final String RESULT = "result"; + private static final String METHOD = "method"; + private static final String ARGS = "args"; + private static final String P = "p"; + private static final String ERROR = "error"; + + /** + * 请求Controller 日志处理 + * + * @param pjp {@link ProceedingJoinPoint} + */ + @Around(value = "@annotation(audit)", argNames = "pjp,audit") + public Object around(ProceedingJoinPoint pjp, Audit audit) throws Throwable { + boolean success = true; + Object[] parameter; + String result = ""; + StandardEvaluationContext context = new StandardEvaluationContext(); + context.setRootObject(new AuditExpressionRoot()); + context.setBeanResolver(new BeanFactoryResolver(applicationContext)); + MethodSignature signature = (MethodSignature) pjp.getSignature(); + String[] parameterNames = parameterNameDiscoverer.getParameterNames(signature.getMethod()); + Map parameterMap = Maps.newHashMap(); + if (parameterNames != null && parameterNames.length > 0) { + //获取方法参数值 + parameter = pjp.getArgs(); + for (int i = 0; i < parameter.length; i++) { + if (ObjectUtils.isNotEmpty(parameter[i])) { + context.setVariable(METHOD, signature.getMethod()); + context.setVariable(ARGS, pjp.getArgs()); + // #参数名 + context.setVariable(parameterNames[i], parameter[i]); + // #p0. + context.setVariable(P + i, parameter[i]); + parameterMap.put(parameterNames[i], parameter[i]); + } + } + } + Object proceed; + try { + proceed = pjp.proceed(); + //结果 + context.setVariable(RESULT, proceed); + } catch (Throwable e) { + success = false; + context.setVariable(ERROR, e); + throw e; + } + //正常、还是异常,都会走以下逻辑 + finally { + //@formatter:off + //内容 + Object content = null; + if (StringUtils.isNoneBlank(audit.content())) { + content = spelExpressionParser.parseExpression(audit.content()).getValue(context); + if (!Objects.isNull(content)) { + try { + content = audit.type().getDesc() + COLON + JSON.toJSONString(content); + } catch (Exception e) { + content = audit.type().getDesc() + COLON + content; + } + } + //后面有设置的内容,拼接 + if (!Objects.isNull(content) && StringUtils.isNoneBlank(AuditContext.getContent())) { + content = content + "," + spelExpressionParser.parseExpression(AuditContext.getContent()).getValue(context); + } + } + //上下文内容,自动拼接事件类型描述 + if (Objects.isNull(content) && StringUtils.isNoneBlank(AuditContext.getContent())) { + content = audit.type().getDesc() +COLON + AuditContext.getContent(); + } + if (Objects.isNull(content) && StringUtils.isBlank(AuditContext.getContent())) { + content = audit.type().getDesc(); + } + content=(content == null) ? "" : content.toString(); + //结果 + Object resultObject = spelExpressionParser.parseExpression("#" + RESULT).getValue(context); + if (!Objects.isNull(resultObject)) { + try { + if (resultObject instanceof ApiRestResult) { + success=((ApiRestResult) resultObject).getSuccess(); + } + result = JSON.toJSONString(resultObject); + } catch (Exception e) { + result = resultObject.toString(); + } + } + //错误 + if (!success) { + Object error = spelExpressionParser.parseExpression("#" + ERROR).getValue(context); + if (!Objects.isNull(error)) { + result= JSON.toJSONString(error, JSONWriter.Feature.PrettyFormat); + } + } + //@formatter:on + auditEventPublish.publish(audit.type(), parameterMap, content.toString(), + AuditContext.getTarget(), result, success ? EventStatus.SUCCESS : EventStatus.FAIL); + } + //Remove AuditContext + AuditContext.removeAuditContext(); + return proceed; + } +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/AuditExpressionOperations.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/AuditExpressionOperations.java new file mode 100644 index 00000000..72882be8 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/AuditExpressionOperations.java @@ -0,0 +1,45 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.annotation; + +import org.springframework.security.core.Authentication; + +/** + * Audit 解析器 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/28 22:45 + */ +public interface AuditExpressionOperations { + + /** + * Gets the {@link Authentication} used for evaluating the expressions + * + * @return the {@link Authentication} for evaluating the expressions + */ + Authentication getAuthentication(); + + /** + * 转JSON字符串 + * + * @param object {@link Object} + * @return {@link String} + */ + String toJsonString(Object object); + +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/AuditExpressionRoot.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/AuditExpressionRoot.java new file mode 100644 index 00000000..6b2c4eca --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/AuditExpressionRoot.java @@ -0,0 +1,57 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.annotation; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +import com.alibaba.fastjson2.JSON; + +import lombok.AllArgsConstructor; + +/** + * AuditExpressionRoot + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/28 22:48 + */ +@AllArgsConstructor +public class AuditExpressionRoot implements AuditExpressionOperations { + + /** + * Gets the {@link Authentication} used for evaluating the expressions + * + * @return the {@link Authentication} for evaluating the expressions + */ + @Override + public Authentication getAuthentication() { + return SecurityContextHolder.getContext().getAuthentication(); + } + + /** + * 转为JSON字符串 + * + * @param object {@link Object} + * @return {@link String} + */ + @Override + public String toJsonString(Object object) { + return JSON.toJSONString(object); + } + +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/package-info.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/package-info.java new file mode 100644 index 00000000..653cae2f --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/annotation/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.annotation; \ No newline at end of file diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/configuration/ElasticsearchConfiguration.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/configuration/ElasticsearchConfiguration.java new file mode 100644 index 00000000..70f97727 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/configuration/ElasticsearchConfiguration.java @@ -0,0 +1,217 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.configuration; + +import java.util.Map; + +import org.jetbrains.annotations.NotNull; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.ReadingConverter; +import org.springframework.data.convert.WritingConverter; +import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.common.collect.Lists; + +import cn.topiam.employee.audit.enums.EventStatus; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.audit.enums.TargetType; +import cn.topiam.employee.common.enums.UserType; +import cn.topiam.employee.common.geo.maxmind.enums.GeoLocationProvider; +import cn.topiam.employee.support.util.JsonUtils; + +/** + * ElasticsearchConfiguration + * + * @author TopIAM + * Created by support@topiam.cn on 2022/11/3 23:31 + */ +@Configuration +public class ElasticsearchConfiguration { + + @Bean + public ElasticsearchCustomConversions elasticsearchCustomConversions() { + return new ElasticsearchCustomConversions( + Lists.newArrayList(AuditTypeToStringConverter.INSTANCE, + StringToAuditTypeConverter.INSTANCE, EventStatusToStringConverter.INSTANCE, + StringToEventStatusConverter.INSTANCE, ActorTypeToStringConverter.INSTANCE, + StringToActorTypeConverter.INSTANCE, GeoLocationProviderToStringConverter.INSTANCE, + StringToGeoLocationProviderConverter.INSTANCE, TargetTypeToStringConverter.INSTANCE, + StringToTargetTypeConverter.INSTANCE, StringToMapConverter.INSTANCE, + MapToStringConverter.INSTANCE)); + } + + @WritingConverter + enum AuditTypeToStringConverter implements Converter { + /** + * INSTANCE + */ + INSTANCE,; + + @Override + public String convert(EventType source) { + return source.getCode(); + } + } + + @ReadingConverter + enum StringToAuditTypeConverter implements Converter { + /** + *INSTANCE + */ + INSTANCE; + + @Override + public EventType convert(@NotNull String source) { + return EventType.getType(source); + } + } + + @WritingConverter + enum ActorTypeToStringConverter implements Converter { + /** + * INSTANCE + */ + INSTANCE,; + + @Override + public String convert(UserType source) { + return source.getCode(); + } + } + + @ReadingConverter + enum StringToActorTypeConverter implements Converter { + /** + *INSTANCE + */ + INSTANCE; + + @Override + public UserType convert(@NotNull String source) { + return UserType.getType(source); + } + } + + @WritingConverter + enum TargetTypeToStringConverter implements Converter { + /** + * INSTANCE + */ + INSTANCE,; + + @Override + public String convert(TargetType source) { + return source.getCode(); + } + } + + @ReadingConverter + enum StringToTargetTypeConverter implements Converter { + /** + *INSTANCE + */ + INSTANCE; + + @Override + public TargetType convert(@NotNull String source) { + return TargetType.getType(source); + } + } + + @WritingConverter + enum GeoLocationProviderToStringConverter implements Converter { + /** + * INSTANCE + */ + INSTANCE,; + + @Override + public String convert(GeoLocationProvider source) { + return source.getCode(); + } + } + + @ReadingConverter + enum StringToGeoLocationProviderConverter implements Converter { + /** + *INSTANCE + */ + INSTANCE; + + @Override + public GeoLocationProvider convert(@NotNull String source) { + return GeoLocationProvider.getType(source); + } + } + + @WritingConverter + enum MapToStringConverter implements Converter, String> { + /** + * INSTANCE + */ + INSTANCE,; + + @Override + public String convert(@NotNull Map source) { + return JsonUtils.writeValueAsString(source); + } + } + + @ReadingConverter + enum StringToMapConverter implements Converter> { + /** + *INSTANCE + */ + INSTANCE; + + @Override + public Map convert(@NotNull String source) { + return JsonUtils.readValue(source, new TypeReference<>() { + }); + } + } + + @WritingConverter + enum EventStatusToStringConverter implements Converter { + /** + * INSTANCE + */ + INSTANCE,; + + @Override + public String convert(@NotNull EventStatus source) { + return source.getCode(); + } + } + + @ReadingConverter + enum StringToEventStatusConverter implements Converter { + /** + *INSTANCE + */ + INSTANCE; + + @Override + public EventStatus convert(@NotNull String source) { + return EventStatus.getType(source); + } + } +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/context/AuditContext.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/context/AuditContext.java new file mode 100644 index 00000000..e40679d8 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/context/AuditContext.java @@ -0,0 +1,186 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.context; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.springframework.util.CollectionUtils; + +import com.alibaba.ttl.TransmittableThreadLocal; + +import cn.topiam.employee.audit.entity.Target; + +/** + * AuditContext + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/23 22:39 + */ +public class AuditContext { + + /** + * 内容 + */ + private static final TransmittableThreadLocal CONTENT = new TransmittableThreadLocal<>(); + + /** + * 目标对象 + */ + private static final TransmittableThreadLocal> TARGET_LIST = new TransmittableThreadLocal<>(); + + /** + * 额外数据 + */ + private static final TransmittableThreadLocal> ADDITIONAL_DATA = new TransmittableThreadLocal<>(); + + /** + * Get Content + * + * @return {@link Object} + */ + public static String getContent() { + return CONTENT.get(); + } + + /** + * Set Content + */ + public static void setContent(String content) { + CONTENT.set(content); + } + + /** + * Remove Content + */ + public static void removeContent() { + CONTENT.remove(); + } + + /** + * Get Additional Content + * + * @return {@link Object} + */ + public static Object getAdditionalData(String key) { + return getAdditionalData().get(key); + } + + /** + * Get + * + * @return {@link Map} + */ + public static Map getAdditionalData() { + Map values = ADDITIONAL_DATA.get(); + if (CollectionUtils.isEmpty(values)) { + ADDITIONAL_DATA.set(new HashMap<>(16)); + } + return values; + } + + /** + * PUT + * + * @param key {@link String} + * @param value {@link Object} + */ + public static void putAdditionalData(String key, Object value) { + Map values = ADDITIONAL_DATA.get(); + if (CollectionUtils.isEmpty(values)) { + HashMap map = new HashMap<>(16); + map.put(key, value); + ADDITIONAL_DATA.set(map); + return; + } + values.put(key, value); + } + + /** + * PUT + * + * @param value {@link Map} + */ + public static void putAdditionalData(Map value) { + ADDITIONAL_DATA.set(value); + } + + /** + * Get Target + * + * @return {@link Object} + */ + public static List getTarget() { + return TARGET_LIST.get(); + } + + /** + * Set Target + */ + public static void setTarget(Target... target) { + if (!Objects.isNull(target)) { + TARGET_LIST.set(List.of(target)); + } + } + + /** + * Set Target + */ + public static void setTarget(List targets) { + if (!CollectionUtils.isEmpty(targets)) { + TARGET_LIST.set(targets); + } + } + + /** + * Remove Content + */ + public static void removeTarget() { + TARGET_LIST.remove(); + } + + /** + * remove + */ + public static void removeAdditionalData() { + ADDITIONAL_DATA.remove(); + } + + /** + * remove + * + * @param key {@link String} + */ + public static void removeAdditionalData(String key) { + Map values = ADDITIONAL_DATA.get(); + if (!CollectionUtils.isEmpty(values)) { + values.remove(key); + } + } + + /** + * remove + */ + public static void removeAuditContext() { + removeAdditionalData(); + removeContent(); + removeTarget(); + } +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/controller/AuditController.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/controller/AuditController.java new file mode 100644 index 00000000..5c18d79d --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/controller/AuditController.java @@ -0,0 +1,91 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.controller; + +import java.util.List; + +import javax.validation.constraints.NotNull; + +import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import cn.topiam.employee.audit.controller.pojo.AuditDictResult; +import cn.topiam.employee.audit.controller.pojo.AuditListQuery; +import cn.topiam.employee.audit.controller.pojo.AuditListResult; +import cn.topiam.employee.audit.service.AuditService; +import cn.topiam.employee.common.constants.AuditConstants; +import cn.topiam.employee.common.enums.UserType; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 系统审计 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/23 21:12 + */ +@Validated +@Tag(name = "系统审计") +@RestController +@RequestMapping(value = AuditConstants.AUDIT_PATH, produces = MediaType.APPLICATION_JSON_VALUE) +@AllArgsConstructor +public class AuditController { + + /** + * 审计列表查询 + * + * @param query {@link AuditListQuery} + * @param pageModel {@link PageModel} + * @return {@link ApiRestResult} + */ + @Operation(description = "查询审计列表") + @GetMapping(value = "/list") + public ApiRestResult> getAuditList(@Validated AuditListQuery query, + PageModel pageModel) { + Page list = auditService.getAuditList(query, pageModel); + return ApiRestResult.ok(list); + } + + /** + * 获取审计字典类型 + * + * @return {@link ApiRestResult} + */ + @Validated + @Operation(description = "获取审计类型") + @GetMapping(value = "/types/{user_type}") + public ApiRestResult> getAuditDict(@PathVariable(name = "user_type") @NotNull(message = "用户类型不能为空!") UserType userType) { + List dict = auditService.getAuditDict(userType); + return ApiRestResult.ok(dict); + } + + /** + * AuditService + */ + private final AuditService auditService; +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/controller/pojo/AuditDictResult.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/controller/pojo/AuditDictResult.java new file mode 100644 index 00000000..1c2a2f4d --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/controller/pojo/AuditDictResult.java @@ -0,0 +1,63 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.controller.pojo; + +import java.util.Set; + +import org.springdoc.api.annotations.ParameterObject; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 审计字典结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/27 22:35 + */ +@Data +@Schema(description = "审计字典结果") +@ParameterObject +public class AuditDictResult { + + @Parameter(description = "分组名") + private String name; + @Parameter(description = "分组编码") + private String code; + @Parameter(description = "类型") + private Set types; + + @Data + @Schema(description = "审计类型") + public static class AuditType { + /** + * 名称 + */ + @Parameter(description = "名称") + private String name; + + /** + * CODE + */ + @Parameter(description = "CODE") + private String code; + } + +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/controller/pojo/AuditListQuery.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/controller/pojo/AuditListQuery.java new file mode 100644 index 00000000..e84119e5 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/controller/pojo/AuditListQuery.java @@ -0,0 +1,80 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.controller.pojo; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.List; + +import javax.validation.constraints.NotNull; + +import org.springdoc.api.annotations.ParameterObject; +import org.springframework.format.annotation.DateTimeFormat; + +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.common.enums.UserType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import static cn.topiam.employee.support.constant.EiamConstants.DEFAULT_DATE_TIME_FORMATTER_PATTERN; + +/** + * 查询审计日志列表入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/23 21:22 + */ +@Data +@Schema(description = "查询审计日志列表入参") +@ParameterObject +public class AuditListQuery implements Serializable { + + /** + * 用户名 + */ + @Parameter(description = "用户名") + private String username; + /** + * 审计类型 + */ + @Parameter(description = "审计类型") + private List eventType; + + /** + * 用户类型 + */ + @NotNull(message = "用户类型不能为空") + @Parameter(description = "用户类型") + private UserType userType; + + /** + * 事件开始时间 + */ + @Parameter(description = "事件开始时间") + @DateTimeFormat(pattern = DEFAULT_DATE_TIME_FORMATTER_PATTERN) + private LocalDateTime startEventTime; + + /** + * 事件结束时间 + */ + @Parameter(description = "事件结束时间") + @DateTimeFormat(pattern = DEFAULT_DATE_TIME_FORMATTER_PATTERN) + private LocalDateTime endEventTime; +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/controller/pojo/AuditListResult.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/controller/pojo/AuditListResult.java new file mode 100644 index 00000000..5bacaf49 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/controller/pojo/AuditListResult.java @@ -0,0 +1,105 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.controller.pojo; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import cn.topiam.employee.audit.entity.GeoLocation; +import cn.topiam.employee.audit.entity.Target; +import cn.topiam.employee.audit.entity.UserAgent; +import cn.topiam.employee.audit.enums.EventStatus; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; +import static cn.topiam.employee.support.constant.EiamConstants.DEFAULT_DATE_TIME_FORMATTER_PATTERN; + +/** + * 审计日志列表结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/24 22:07 + */ +@Data +@Schema(description = "审计日志列表结果") +public class AuditListResult implements Serializable { + /** + * ID + */ + @Schema(description = "ID") + private String id; + + /** + * 用户 + */ + @Schema(description = "用户名") + private String username; + + /** + * 用户 ID + */ + @Schema(description = "用户ID") + private String userId; + + /** + * 用户类型 + */ + @Schema(description = "用户类型") + private String userType; + + /** + * 用户代理 + */ + @Schema(description = "用户代理") + private UserAgent userAgent; + + /** + * 地理IP + */ + @Schema(description = "地理位置") + private GeoLocation geoLocation; + + /** + * 事件类型 + */ + @Schema(description = "事件类型") + private String eventType; + + /** + * 操作时间 + */ + @JsonFormat(pattern = DEFAULT_DATE_TIME_FORMATTER_PATTERN) + @Schema(description = "事件时间") + private LocalDateTime eventTime; + + /** + * 事件状态 + */ + @Schema(description = "事件状态") + private EventStatus eventStatus; + + /** + * 目标 + */ + @Schema(description = "目标") + private List targets; +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/Actor.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/Actor.java new file mode 100644 index 00000000..109873f3 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/Actor.java @@ -0,0 +1,57 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.entity; + +import java.io.Serial; +import java.io.Serializable; + +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; + +import cn.topiam.employee.common.enums.UserType; + +import lombok.Builder; +import lombok.Data; + +/** + * Actor + * @author TopIAM + * Created by support@topiam.cn on 2022/11/5 23:30 + */ +@Data +@Builder +public class Actor implements Serializable { + + public static final String ACTOR_ID = "actor.id"; + public static final String ACTOR_TYPE = "actor.type"; + + @Serial + private static final long serialVersionUID = -1144169992714000310L; + /** + * 行动者ID + */ + @Field(type = FieldType.Keyword, name = "id") + private String id; + + /** + * 行动者类型 + */ + @Field(type = FieldType.Keyword, name = "type") + private UserType type; + +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/AuditElasticSearchEntity.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/AuditElasticSearchEntity.java new file mode 100644 index 00000000..93c4e6a8 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/AuditElasticSearchEntity.java @@ -0,0 +1,98 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.entity; + +import java.io.Serial; +import java.io.Serializable; +import java.time.Instant; +import java.util.List; + +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; + +import lombok.Builder; +import lombok.Data; +import static org.springframework.data.elasticsearch.annotations.DateFormat.date_hour_minute_second_millis; + +/** + * 审计elasticsearch实体 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/10/13 23:22 + */ +@Data +@Builder +public class AuditElasticSearchEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 6589338521638519634L; + + @Id + @Field(type = FieldType.Keyword, name = "id") + private String id; + + /** + * Request Id + */ + @Field(type = FieldType.Keyword, name = "request_id") + private String requestId; + + /** + * Session Id + */ + @Field(type = FieldType.Keyword, name = "session_id") + private String sessionId; + + /** + * 操作者 + */ + @Field(type = FieldType.Object, name = "actor") + private Actor actor; + + /** + * 事件 + */ + @Field(type = FieldType.Object, name = "event") + private Event event; + + /** + * 操作目标 + */ + @Field(type = FieldType.Object, name = "target") + private List targets; + + /** + * UserAgent + */ + @Field(type = FieldType.Object, name = "user_agent") + private UserAgent userAgent; + + /** + * 地理位置 + */ + @Field(type = FieldType.Object, name = "geo_location") + private GeoLocation geoLocation; + + /** + * 时间戳 + */ + @Field(type = FieldType.Date, name = "@timestamp", format = date_hour_minute_second_millis) + private Instant timestamp; + +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/AuditEntity.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/AuditEntity.java new file mode 100644 index 00000000..c61fdd80 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/AuditEntity.java @@ -0,0 +1,139 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.entity; + +import java.io.Serial; +import java.time.Instant; +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import org.hibernate.annotations.Type; + +import cn.topiam.employee.audit.enums.EventStatus; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.common.enums.UserType; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + * 审计 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/8/1 21:41 + */ +@Getter +@Setter +@ToString +@RequiredArgsConstructor +@Accessors(chain = true) +@Entity +@Table(name = "audit") +public class AuditEntity extends BaseEntity { + + @Serial + private static final long serialVersionUID = -3119319193111206582L; + + /** + * Request Id + */ + @Column(name = "request_id") + private String requestId; + + /** + * Session Id + */ + @Column(name = "session_id") + private String sessionId; + + /** + * 操作目标 + */ + @Type(type = "json") + @Column(name = "target_") + private List targets; + + /** + * UserAgent + */ + @Type(type = "json") + @Column(name = "user_agent") + private UserAgent userAgent; + + /** + * 地理位置 + */ + @Type(type = "json") + @Column(name = "geo_location") + private GeoLocation geoLocation; + + /** + * 审计事件类型 + */ + @Column(name = "event_type") + private EventType eventType; + + /** + * 参数 + */ + @Column(name = "event_param") + private String eventParam; + + /** + * 事件内容 + */ + @Column(name = "event_content") + private String eventContent; + + /** + * 事件结果 + */ + @Column(name = "event_result") + private String eventResult; + + /** + * 事件时间 + */ + @Column(name = "event_time") + private Instant eventTime; + + /** + * 事件状态 + */ + @Column(name = "event_status") + private EventStatus eventStatus; + + /** + * 操作者ID + */ + @Column(name = "actor_id") + private String actorId; + + /** + * 操作人类型 + */ + @Column(name = "actor_type") + private UserType actorType; +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/Event.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/Event.java new file mode 100644 index 00000000..d9b38412 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/Event.java @@ -0,0 +1,88 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.entity; + +import java.io.Serial; +import java.io.Serializable; +import java.time.Instant; + +import org.springframework.data.elasticsearch.annotations.DateFormat; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; + +import cn.topiam.employee.audit.enums.EventStatus; +import cn.topiam.employee.audit.enums.EventType; + +import lombok.Builder; +import lombok.Data; + +/** + * Event + * + * @author TopIAM + * Created by support@topiam.cn on 2022/11/5 23:33 + */ +@Data +@Builder +public class Event implements Serializable { + + @Serial + private static final long serialVersionUID = -1144169992714000310L; + + public static final String EVENT_TYPE = "event.type"; + public static final String EVENT_TIME = "event.time"; + + public static final String EVENT_STATUS = "event.status.keyword"; + + /** + * 审计事件类型 + */ + @Field(type = FieldType.Keyword, name = "type") + private EventType type; + + /** + * 参数 + */ + @Field(type = FieldType.Text, name = "param") + private String param; + + /** + * 事件内容 + */ + @Field(type = FieldType.Text, name = "content") + private String content; + + /** + * 事件结果 + */ + @Field(type = FieldType.Text, name = "result") + private String result; + + /** + * 事件时间 + */ + @Field(type = FieldType.Date, name = "time", format = DateFormat.date_hour_minute_second_millis) + private Instant time; + + /** + * 事件状态 + */ + @Field(type = FieldType.Keyword, name = "status") + private EventStatus status; + +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/GeoLocation.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/GeoLocation.java new file mode 100644 index 00000000..be15f345 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/GeoLocation.java @@ -0,0 +1,112 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.entity; + +import java.io.Serial; +import java.io.Serializable; + +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; +import org.springframework.data.elasticsearch.annotations.GeoPointField; +import org.springframework.data.elasticsearch.core.geo.GeoPoint; + +import cn.topiam.employee.common.geo.maxmind.enums.GeoLocationProvider; + +import lombok.Builder; +import lombok.Data; + +/** + * 地理位置 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/11/5 23:31 + */ +@Data +@Builder +public class GeoLocation implements Serializable { + + @Serial + private static final long serialVersionUID = -1144169992714000310L; + + /** + * IP + */ + @Field(type = FieldType.Ip, name = "ip") + private String ip; + + /** + * continent code + */ + @Field(type = FieldType.Keyword, name = "continent_code") + private String continentCode; + + /** + * continent Name + */ + @Field(type = FieldType.Text, name = "continent_code") + private String continentName; + + /** + * 国家code + */ + @Field(type = FieldType.Keyword, name = "country_code") + private String countryCode; + + /** + * 国家名称 + */ + @Field(type = FieldType.Text, name = "country_name") + private String countryName; + + /** + * 省份code + */ + @Field(type = FieldType.Keyword, name = "province_code") + private String provinceCode; + + /** + * 省份 + */ + @Field(type = FieldType.Text, name = "province_name") + private String provinceName; + + /** + * 城市code + */ + @Field(type = FieldType.Keyword, name = "city_code") + private String cityCode; + + /** + * 城市名称 + */ + @Field(type = FieldType.Text, name = "city_name") + private String cityName; + + /** + * 地理坐标 + */ + @GeoPointField + private GeoPoint point; + + /** + * 提供商 + */ + @Field(type = FieldType.Keyword, name = "provider") + private GeoLocationProvider provider; + +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/Target.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/Target.java new file mode 100644 index 00000000..5a91890f --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/Target.java @@ -0,0 +1,58 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.entity; + +import java.io.Serial; +import java.io.Serializable; + +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; + +import cn.topiam.employee.audit.enums.TargetType; + +import lombok.Builder; +import lombok.Data; + +/** + * Target + * + * @author TopIAM + * Created by support@topiam.cn on 2022/11/5 23:34 + */ +@Data +@Builder +public class Target implements Serializable { + + @Serial + private static final long serialVersionUID = -1144169992714000310L; + + public static final String TARGET_ID_KEYWORD = "target.id.keyword"; + + /** + * 目标 ID + */ + @Field(type = FieldType.Keyword, name = "id") + private String id; + /** + * + * 目标类型 + */ + @Field(type = FieldType.Keyword, name = "type") + private TargetType type; + +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/UserAgent.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/UserAgent.java new file mode 100644 index 00000000..03adffb3 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/entity/UserAgent.java @@ -0,0 +1,54 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.entity; + +import java.io.Serializable; + +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; + +import lombok.Builder; +import lombok.Data; + +/** + * UserAgent + * @author TopIAM + * Created by support@topiam.cn on 2022/11/5 23:31 + */ +@Data +@Builder +public class UserAgent implements Serializable { + + @Field(type = FieldType.Keyword, name = "device_type") + private String deviceType; + + @Field(type = FieldType.Keyword, name = "platform") + private String platform; + + @Field(type = FieldType.Keyword, name = "platform_version") + private String platformVersion; + + @Field(type = FieldType.Keyword, name = "browser") + private String browser; + + @Field(type = FieldType.Keyword, name = "browser_type") + private String browserType; + + @Field(type = FieldType.Keyword, name = "browser_major_version") + private String browserMajorVersion; +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/enums/EventStatus.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/enums/EventStatus.java new file mode 100644 index 00000000..18226396 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/enums/EventStatus.java @@ -0,0 +1,68 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +import lombok.Getter; + +/** + * 事件状态 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/11/6 19:57 + */ +@Getter +public enum EventStatus { + /** + * 成功 + */ + SUCCESS("success", "成功"), + /** + * 失败 + */ + FAIL("fail", "失败"); + + @JsonValue + private final String code; + private final String desc; + + EventStatus(String code, String desc) { + this.code = code; + this.desc = desc; + } + + /** + * 获取类型 + * + * @param code {@link String} + * @return {@link EventStatus} + */ + @EnumConvert + public static EventStatus getType(String code) { + EventStatus[] values = values(); + for (EventStatus status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/enums/EventType.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/enums/EventType.java new file mode 100644 index 00000000..158629ba --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/enums/EventType.java @@ -0,0 +1,551 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.enums; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.audit.event.type.*; +import cn.topiam.employee.common.enums.UserType; +import cn.topiam.employee.support.web.converter.EnumConvert; + +import lombok.Getter; + +/** + * 事件类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/29 21:00 + */ +@Getter +public enum EventType { + /** + * 登录门户 + */ + LOGIN_PORTAL(AuthenticationEventType.LOGIN_PORTAL), + + /** + * 登录控制台 + */ + LOGIN_CONSOLE(AuthenticationEventType.LOGIN_CONSOLE), + + /** + * 退出门户 + */ + LOGOUT_PORTAL(AuthenticationEventType.LOGOUT_PORTAL), + + /** + * 退出控制台 + */ + LOGOUT_CONSOLE(AuthenticationEventType.LOGOUT_CONSOLE), + + /** + * 注册 + */ + USER_REGISTER(AccountEventType.USER_REGISTER), + + /** + * 修改账户信息 + */ + MODIFY_ACCOUNT_INFO(AccountEventType.MODIFY_ACCOUNT_INFO), + + /** + * 修改邮箱 + */ + MODIFY_USER_EMAIL(AccountEventType.MODIFY_USER_EMAIL), + + /** + * 修改手机号 + */ + MODIFY_USER_PHONE(AccountEventType.MODIFY_USER_PHONE), + + /** + * 修改密码 + */ + MODIFY_USER_PASSWORD(AccountEventType.MODIFY_USER_PASSWORD), + + /** + * 创建用户 + */ + CREATE_USER(AccountEventType.CREATE_USER), + + /** + * 编辑用户 + */ + UPDATE_USER(AccountEventType.UPDATE_USER), + + /** + * 删除用户 + */ + DELETE_USER(AccountEventType.DELETE_USER), + + /** + * 禁用用户 + */ + DISABLE_USER(AccountEventType.DISABLE_USER), + + /** + * 启用用户 + */ + ENABLE_USER(AccountEventType.ENABLE_USER), + /** + * 绑定账号 + */ + BIND_IDP_USER(AccountEventType.BIND_IDP_USER), + /** + * 解绑账号 + */ + UNBIND_IDP_USER(AccountEventType.UNBIND_IDP_USER), + /** + * 解绑应用用户 + */ + UNBIND_APPLICATION_USER(AccountEventType.UNBIND_APPLICATION_USER), + /** + * 创建组织 + */ + CREATE_ORG(AccountEventType.CREATE_ORG), + /** + * 编辑组织 + */ + UPDATE_ORG(AccountEventType.UPDATE_ORG), + /** + * 删除组织 + */ + DELETE_ORG(AccountEventType.DELETE_ORGANIZATION), + /** + * 删除组织 + */ + MOVE_ORGANIZATION(AccountEventType.MOVE_ORGANIZATION), + /** + * 添加用户到组织 + */ + USER_ADD_ORG(AccountEventType.USER_ADD_ORG), + /** + * 用户转岗到其他组织 + */ + USER_TRANSFER_ORG(AccountEventType.USER_TRANSFER_ORG), + /** + * 从组织中移除用户 + */ + ORG_REMOVE_USER(AccountEventType.ORG_REMOVE_USER), + /** + * 登录应用 + */ + APP_SSO(AppEventType.APP_SSO), + /** + * 退出应用 + */ + SIGN_OUT_APP(AppEventType.SIGN_OUT_APP), + /** + * 添加应用 + */ + ADD_APP(AppEventType.ADD_APP), + /** + * 启用应用 + */ + ENABLE_APP(AppEventType.ENABLE_APP), + /** + * 禁用应用 + */ + DISABLE_APP(AppEventType.DISABLE_APP), + /** + * 修改应用 + */ + UPDATE_APP(AppEventType.UPDATE_APP), + /** + * 保存应用配置 + */ + SAVE_APP_CONFIG(AppEventType.SAVE_APP_CONFIG), + /** + * 删除应用 + */ + DELETE_APP(AppEventType.DELETE_APP), + /** + * 应用授权 + */ + APP_AUTHORIZATION(AppEventType.APP_ACCESS_POLICY), + /** + * 删除应用授权 + */ + APP_DELETE_ACCESS_POLICY(AppEventType.APP_DELETE_ACCESS_POLICY), + /** + * 添加应用账户 + */ + ADD_APP_ACCOUNT(AppEventType.ADD_APP_ACCOUNT), + /** + * 删除应用账户 + */ + DELETE_APP_ACCOUNT(AppEventType.DELETE_APP_ACCOUNT), + /** + * 保存SSO配置 + */ + SAVE_SSO_CONFIG(AppEventType.SAVE_SSO_CONFIG), + /** + * 添加身份提供商 + */ + ADD_IDP(AuthenticationEventType.ADD_IDP), + /** + * 编辑身份提供商 + */ + UPDATE_IDP(AuthenticationEventType.UPDATE_IDP), + /** + * 启用认证提供商 + */ + ENABLE_IDP(AuthenticationEventType.ENABLE_IDP), + /** + * 禁用认证提供商 + */ + DISABLE_IDP(AuthenticationEventType.DISABLE_IDP), + + /** + * 删除认证提供商 + */ + DELETE_IDP(AuthenticationEventType.DELETE_IDP), + + /** + * 保存安全基础设置 + */ + SAVE_LOGIN_SECURITY_BASIC_SETTINGS(SettingEventType.SAVE_LOGIN_SECURITY_BASIC_SETTINGS), + /** + * 密码策略 + */ + SAVE_PASSWORD_POLICY_SETTINGS(SettingEventType.SAVE_PASSWORD_POLICY_SETTINGS), + + /** + * 多因素认证 + */ + SAVE_MFA_SETTINGS(SettingEventType.SAVE_MFA_SETTINGS), + + /** + * 行为验证码 + */ + SAVE_CAPTCHA_PROVIDER(SettingEventType.SAVE_CAPTCHA_PROVIDER), + + /** + * 禁用行为验证码 + */ + OFF_CAPTCHA_PROVIDER(SettingEventType.OFF_CAPTCHA_PROVIDER), + + /** + * 添加管理员 + */ + ADD_ADMINISTRATOR(SettingEventType.ADD_ADMINISTRATOR), + /** + * 删除管理员 + */ + DELETE_ADMINISTRATOR(SettingEventType.DELETE_ADMINISTRATOR), + /** + * 修改管理员 + */ + UPDATE_ADMINISTRATOR(SettingEventType.UPDATE_ADMINISTRATOR), + /** + * 启用管理员 + */ + ENABLE_ADMINISTRATOR(SettingEventType.ENABLE_ADMINISTRATOR), + /** + * 禁用管理员 + */ + DISABLE_ADMINISTRATOR(SettingEventType.DISABLE_ADMINISTRATOR), + /** + * 重置管理员密码 + */ + RESET_ADMINISTRATOR_PASSWORD(SettingEventType.RESET_ADMINISTRATOR_PASSWORD), + /** + * 开启自定义修改密码邮件模板 + */ + ON_CUSTOMIZE_CHANGE_PASSWORD_MAIL(SettingEventType.ON_CUSTOMIZE_CHANGE_PASSWORD_MAIL), + + /** + * 开启自定义重置密码邮件模板 + */ + ON_CUSTOMIZE_RESET_PASSWORD_MAIL(SettingEventType.ON_CUSTOMIZE_RESET_PASSWORD_MAIL), + + /** + * 开启自定义确认重置密码邮件模板 + */ + ON_CUSTOMIZE_CONFIRM_RESET_PASSWORD_MAIL(SettingEventType.ON_CUSTOMIZE_CONFIRM_RESET_PASSWORD_MAIL), + + /** + * 开启自定义验证邮件模板 + */ + ON_CUSTOMIZE_VERIFY_MAIL(SettingEventType.ON_CUSTOMIZE_VERIFY_MAIL), + /** + * 开启自定义欢迎邮件模板 + */ + ON_CUSTOMIZE_WELCOME_MAIL(SettingEventType.ON_CUSTOMIZE_WELCOME_MAIL), + /** + * 开启自定义修改绑定邮件模板 + */ + ON_CUSTOMIZE_MODIFY_BINDING_MAIL(SettingEventType.ON_CUSTOMIZE_MODIFY_BINDING_MAIL), + /** + * 关闭自定义修改密码邮件模板 + */ + OFF_CUSTOMIZE_CHANGE_PASSWORD_MAIL(SettingEventType.OFF_CUSTOMIZE_CHANGE_PASSWORD_MAIL), + + /** + * 关闭自定义重置密码邮件模板 + */ + OFF_CUSTOMIZE_RESET_PASSWORD_MAIL(SettingEventType.OFF_CUSTOMIZE_RESET_PASSWORD_MAIL), + + /** + * 关闭自定义确认重置密码邮件模板 + */ + OFF_CUSTOMIZE_CONFIRM_RESET_PASSWORD_MAIL(SettingEventType.OFF_CUSTOMIZE_CONFIRM_RESET_PASSWORD_MAIL), + + /** + * 关闭自定义验证邮件模板 + */ + OFF_CUSTOMIZE_VERIFY_MAIL(SettingEventType.OFF_CUSTOMIZE_VERIFY_MAIL), + /** + * 关闭自定义欢迎邮件模板 + */ + OFF_CUSTOMIZE_WELCOME_MAIL(SettingEventType.OFF_CUSTOMIZE_WELCOME_MAIL), + /** + * 关闭自定义修改绑定邮件模板 + */ + OFF_CUSTOMIZE_MODIFY_BINDING_MAIL(SettingEventType.OFF_CUSTOMIZE_MODIFY_BINDING_MAIL), + + /** + * 保存自定义修改密码邮件模板 + */ + SAVE_CUSTOMIZE_CHANGE_PASSWORD_MAIL(SettingEventType.SAVE_CUSTOMIZE_CHANGE_PASSWORD_MAIL), + + /** + * 保存自定义重置密码邮件模板 + */ + SAVE_CUSTOMIZE_RESET_PASSWORD_MAIL(SettingEventType.SAVE_CUSTOMIZE_RESET_PASSWORD_MAIL), + + /** + * 保存自定义确认重置密码邮件模板 + */ + SAVE_CUSTOMIZE_CONFIRM_RESET_PASSWORD_MAIL(SettingEventType.SAVE_CUSTOMIZE_CONFIRM_RESET_PASSWORD_MAIL), + + /** + * 保存自定义验证邮件模板 + */ + SAVE_CUSTOMIZE_VERIFY_MAIL(SettingEventType.SAVE_CUSTOMIZE_VERIFY_MAIL), + /** + * 保存自定义欢迎邮件模板 + */ + SAVE_CUSTOMIZE_WELCOME_MAIL(SettingEventType.SAVE_CUSTOMIZE_WELCOME_MAIL), + /** + * 保存自定义修改绑定邮件模板 + */ + SAVE_CUSTOMIZE_MODIFY_BINDING_MAIL(SettingEventType.SAVE_CUSTOMIZE_MODIFY_BINDING_MAIL), + + /** + * 开启邮件服务 + */ + ON_MAIL_SERVICE(SettingEventType.ON_MAIL_SERVICE), + /** + * 关闭邮件服务 + */ + OFF_MAIL_SERVICE(SettingEventType.OFF_MAIL_SERVICE), + + /** + * 保存邮件服务 + */ + SAVE_MAIL_SERVICE(SettingEventType.SAVE_MAIL_SERVICE), + + /** + * 开启短信验证服务 + */ + ON_SMS_SERVICE(SettingEventType.ON_SMS_SERVICE), + + /** + * 关闭短信验证服务 + */ + OFF_SMS_SERVICE(SettingEventType.OFF_SMS_SERVICE), + + /** + * 保存短信验证服务 + */ + SAVE_SMS_SERVICE(SettingEventType.SAVE_SMS_SERVICE), + + /** + * 开启存储服务 + */ + ON_STORAGE_SERVICE(SettingEventType.ON_STORAGE_SERVICE), + /** + * 关闭存储服务 + */ + OFF_STORAGE_SERVICE(SettingEventType.OFF_STORAGE_SERVICE), + /** + * 保存存储服务 + */ + SAVE_STORAGE_SERVICE(SettingEventType.SAVE_STORAGE_SERVICE), + /** + * 开启地理位置服务 + */ + ON_GEO_LOCATION_SERVICE(SettingEventType.ON_GEO_LOCATION_SERVICE), + /** + * 关闭地理位置服务 + */ + OFF_GEO_LOCATION_SERVICE(SettingEventType.OFF_GEO_LOCATION_SERVICE), + /** + * 保存地理位置服务 + */ + SAVE_GEO_LOCATION_SERVICE(SettingEventType.SAVE_GEO_LOCATION_SERVICE), + + /** + * 下线会话 + */ + DOWN_LINE_SESSION(OtherEventType.DOWN_LINE_SESSION), + + /** + * 批量下线会话 + */ + BATCH_DOWN_LINE_SESSION(OtherEventType.BATCH_DOWN_LINE_SESSION), + + /** + * 创建用户组 + */ + CREATE_USER_GROUP(AccountEventType.CREATE_USER_GROUP), + + /** + * 修改用户组 + */ + UPDATE_USER_GROUP(AccountEventType.UPDATE_USER_GROUP), + + /** + * 删除用户组 + */ + DELETE_USER_GROUP(AccountEventType.DELETE_USER_GROUP), + + /** + * 添加用户组成员 + */ + ADD_USER_GROUP_MEMBER(AccountEventType.ADD_USER_GROUP_MEMBER), + + /** + * 移除用户组成员 + */ + REMOVE_USER_GROUP_MEMBER(AccountEventType.REMOVE_USER_GROUP_MEMBER), + + /** + * 创建身份源 + */ + CREATE_IDENTITY_RESOURCE(AccountEventType.CREATE_IDENTITY_RESOURCE), + + /** + * 修改身份源 + */ + UPDATE_IDENTITY_RESOURCE(AccountEventType.UPDATE_IDENTITY_RESOURCE), + + /** + * 删除身份源 + */ + DELETE_IDENTITY_RESOURCE(AccountEventType.DELETE_IDENTITY_RESOURCE), + + /** + * 启用身份源 + */ + ENABLE_IDENTITY_RESOURCE(AccountEventType.ENABLE_IDENTITY_RESOURCE), + + /** + * 禁用身份源 + */ + DISABLE_IDENTITY_RESOURCE(AccountEventType.DISABLE_IDENTITY_RESOURCE), + /** + * 身份源同步 + */ + IDENTITY_RESOURCE_SYNC(AccountEventType.IDENTITY_RESOURCE_SYNC), + + /** + * 保存应用资源 + */ + SAVE_APP_PERMISSION_RESOURCE(AppEventType.SAVE_APP_PERMISSION_RESOURCE), + /** + * 修改应用资源 + */ + UPDATE_APP_PERMISSION_RESOURCE(AppEventType.UPDATE_APP_PERMISSION_RESOURCE), + /** + * 删除应用资源 + */ + DELETE_APP_PERMISSION_RESOURCE(AppEventType.DELETE_APP_PERMISSION_RESOURCE), + /** + * 启用应用资源 + */ + ENABLE_APP_PERMISSION_RESOURCE(AppEventType.ENABLE_APP_PERMISSION_RESOURCE), + /** + * 禁用应用资源 + */ + DISABLE_APP_PERMISSION_RESOURCE(AppEventType.DISABLE_APP_PERMISSION_RESOURCE), + /** + * 删除用户身份提供商绑定 + */ + DELETE_USER_IDP_BIND(AppEventType.DELETE_APP_ACCOUNT), + + /** + * 添加应用角色 + */ + SAVE_APP_PERMISSION_ROLE(AppEventType.SAVE_APP_PERMISSION_ROLE), + + /** + * 修改应用角色 + */ + UPDATE_APP_PERMISSION_ROLE(AppEventType.UPDATE_APP_PERMISSION_ROLE), + + /** + * 删除应用角色 + */ + DELETE_APP_PERMISSION_ROLE(AppEventType.DELETE_APP_PERMISSION_ROLE); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + /** + * 用户类型 + */ + private final List userTypes; + /** + * 归属资源 + */ + private final Resource resource; + + EventType(Type type) { + this.code = type.getCode(); + this.desc = type.getName(); + this.resource = type.getResource(); + this.userTypes = type.getUserTypes(); + } + + /** + * 获取审计类型 + * + * @param code {@link String} + * @return {@link EventType} + */ + @EnumConvert + public static EventType getType(String code) { + EventType[] values = values(); + for (EventType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/enums/TargetType.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/enums/TargetType.java new file mode 100644 index 00000000..59a1de32 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/enums/TargetType.java @@ -0,0 +1,145 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +import lombok.Getter; + +/** + * 目标类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/10/27 23:46 + */ +@Getter +public enum TargetType { + + /** + * 用户 + */ + USER("user", "用户"), + + /** + * 用户详情 + */ + USER_DETAIL("user_detail", "用户详情"), + + /** + * 用户组 + */ + USER_GROUP("user_group", "用户组"), + + /** + * 用户组成员 + */ + USER_GROUP_MEMBER("user_group_member", "用户组成员"), + + /** + * 身份源 + */ + IDENTITY_SOURCE("identity_source", "身份源"), + + /** + * 组织机构 + */ + ORGANIZATION("organization", "组织机构"), + /** + * 应用 + */ + APPLICATION("application", "应用"), + + /** + * 应用账户 + */ + APPLICATION_ACCOUNT("application_account", "应用账户"), + + /** + * 会话管理 + */ + SESSION("session", "会话管理"), + + /** + * 应用权限 + */ + APP_PERMISSION_RESOURCE("app_permission_resource", "应用权限"), + + /** + * 应用权限策略 + */ + APP_PERMISSION_POLICY("app_permission_policy", "应用权限策略"), + + /** + * 应用权限策略 + */ + APP_PERMISSION_ROLE("app_permission_role", "应用权限角色"), + + /** + * 管理员 + */ + ADMINISTRATOR("administrator", "管理员"), + + /** + * 邮件模版 + */ + MAIL_TEMPLATE("mail_template", "邮件模版"), + + /** + * 身份认证提供商 + */ + IDENTITY_PROVIDER("identity_provider", "身份认证提供商"), + + /** + * 控制台 + */ + CONSOLE("console", "控制台"), + + /** + * 门户端 + */ + PORTAL("portal", "门户端"); + + @JsonValue + private final String code; + private final String desc; + + TargetType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + /** + * 获取类型 + * + * @param code {@link String} + * @return {@link EventType} + */ + @EnumConvert + public static TargetType getType(String code) { + TargetType[] values = values(); + for (TargetType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } + +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/enums/converter/AuditTypeConverter.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/enums/converter/AuditTypeConverter.java new file mode 100644 index 00000000..d7c0b3a7 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/enums/converter/AuditTypeConverter.java @@ -0,0 +1,67 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.audit.enums.EventType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2021/11/10 23:02 + */ +@Converter(autoApply = true) +public class AuditTypeConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(EventType attribute) { + if (Objects.isNull(attribute)) { + return null; + } + return attribute.getCode(); + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public EventType convertToEntityAttribute(String dbData) { + return EventType.getType(dbData); + } +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/enums/converter/EventStatusConverter.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/enums/converter/EventStatusConverter.java new file mode 100644 index 00000000..18cf27fd --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/enums/converter/EventStatusConverter.java @@ -0,0 +1,67 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.audit.enums.EventStatus; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2021/11/10 23:02 + */ +@Converter(autoApply = true) +public class EventStatusConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(EventStatus attribute) { + if (Objects.isNull(attribute)) { + return null; + } + return attribute.getCode(); + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public EventStatus convertToEntityAttribute(String dbData) { + return EventStatus.getType(dbData); + } +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/enums/converter/TargetTypeConverter.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/enums/converter/TargetTypeConverter.java new file mode 100644 index 00000000..86c83f12 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/enums/converter/TargetTypeConverter.java @@ -0,0 +1,67 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.audit.enums.TargetType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2021/11/10 23:02 + */ +@Converter(autoApply = true) +public class TargetTypeConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(TargetType attribute) { + if (Objects.isNull(attribute)) { + return null; + } + return attribute.getCode(); + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public TargetType convertToEntityAttribute(String dbData) { + return TargetType.getType(dbData); + } +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/event/AuditEvent.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/AuditEvent.java new file mode 100644 index 00000000..916bbe0c --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/AuditEvent.java @@ -0,0 +1,59 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.event; + +import java.io.Serial; +import java.util.*; + +import org.springframework.context.ApplicationEvent; + +import cn.topiam.employee.audit.entity.*; + +import lombok.Getter; + +/** + * 审计事件 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/8/1 21:56 + */ +@Getter +public class AuditEvent extends ApplicationEvent { + @Serial + private static final long serialVersionUID = 3425943796938543659L; + + private final String requestId; + private final String sessionId; + private final Actor actor; + private final Event event; + private final List target; + private final UserAgent userAgent; + private final GeoLocation geoLocationModal; + + public AuditEvent(String requestId, String sessionId, Actor actor, Event event, + UserAgent userAgent, GeoLocation geoLocation, List targets) { + super(requestId); + this.requestId = requestId; + this.sessionId = sessionId; + this.actor = actor; + this.event = event; + this.target = targets; + this.userAgent = userAgent; + this.geoLocationModal = geoLocation; + } +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/event/AuditEventListener.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/AuditEventListener.java new file mode 100644 index 00000000..6a6eecd3 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/AuditEventListener.java @@ -0,0 +1,125 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.event; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Objects; + +import org.springframework.context.ApplicationListener; +import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; + +import cn.topiam.employee.audit.entity.*; +import cn.topiam.employee.audit.repository.*; +import cn.topiam.employee.core.configuration.EiamSupportProperties; +import static cn.topiam.employee.common.constants.AuditConstants.getAuditIndexPrefix; +import static cn.topiam.employee.support.constant.EiamConstants.DEFAULT_DATE_FORMATTER_PATTERN; + +/** + * 事件监听 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/12 22:49 + */ +@Component +public class AuditEventListener implements ApplicationListener { + + /** + * onApplicationEvent + * + * @param auditEvent {@link AuditEvent} + */ + @Override + public void onApplicationEvent(@NonNull AuditEvent auditEvent) { + Event event = auditEvent.getEvent(); + Actor actor = auditEvent.getActor(); + List target = auditEvent.getTarget(); + GeoLocation geoLocation = auditEvent.getGeoLocationModal(); + UserAgent userAgent = auditEvent.getUserAgent(); + //保存数据库 + AuditEntity entity = new AuditEntity(); + try { + entity.setRequestId(auditEvent.getRequestId()); + entity.setSessionId(auditEvent.getSessionId()); + //事件 + entity.setEventType(event.getType()); + entity.setEventContent(event.getContent()); + entity.setEventParam(event.getParam()); + entity.setEventStatus(event.getStatus()); + entity.setEventResult(event.getResult()); + entity.setEventTime(event.getTime()); + //操作目标 + entity.setTargets(target); + entity.setGeoLocation(geoLocation); + entity.setUserAgent(userAgent); + entity.setActorId(actor.getId()); + entity.setActorType(actor.getType()); + auditRepository.save(entity); + } catch (Exception ignored) { + } finally { + if (!Objects.isNull(entity.getId())) { + //保存 Elasticsearch + AuditElasticSearchEntity audit = AuditElasticSearchEntity.builder().build(); + audit.setRequestId(auditEvent.getRequestId()); + audit.setSessionId(auditEvent.getSessionId()); + audit.setId(entity.getId().toString()); + audit.setEvent(event); + audit.setTargets(target); + audit.setGeoLocation(geoLocation); + audit.setUserAgent(userAgent); + audit.setActor(actor); + audit.setTimestamp( + entity.getCreateTime().atZone(ZoneId.systemDefault()).toInstant()); + String auditIndex = getAuditIndexPrefix(eiamSupportProperties.getDemo().isOpen()) + + LocalDate.now().format(DateTimeFormatter + .ofPattern(DEFAULT_DATE_FORMATTER_PATTERN)); + elasticsearchOperations.save(audit, IndexCoordinates.of(auditIndex)); + } + } + + } + + /** + * EiamSupportProperties + */ + private final EiamSupportProperties eiamSupportProperties; + + /** + * AuditRepository + */ + private final AuditRepository auditRepository; + + /** + * ElasticsearchOperations + */ + private final ElasticsearchOperations elasticsearchOperations; + + public AuditEventListener(EiamSupportProperties eiamSupportProperties, + AuditRepository auditRepository, + ElasticsearchOperations elasticsearchOperations) { + this.eiamSupportProperties = eiamSupportProperties; + this.auditRepository = auditRepository; + this.elasticsearchOperations = elasticsearchOperations; + } + +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/event/AuditEventPublish.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/AuditEventPublish.java new file mode 100644 index 00000000..1efc970c --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/AuditEventPublish.java @@ -0,0 +1,327 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.event; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.elasticsearch.core.geo.GeoPoint; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +import com.alibaba.fastjson2.JSONObject; + +import cn.topiam.employee.audit.entity.*; +import cn.topiam.employee.audit.enums.EventStatus; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.common.enums.UserType; +import cn.topiam.employee.common.geo.GeoLocationService; +import cn.topiam.employee.core.security.userdetails.UserDetails; +import cn.topiam.employee.support.context.ServletContextHelp; +import cn.topiam.employee.support.trace.TraceUtils; +import cn.topiam.employee.support.util.IpUtils; +import cn.topiam.employee.support.web.useragent.UserAgentUtils; + +import lombok.AllArgsConstructor; +import static cn.topiam.employee.core.logger.LogAspect.replaceBlank; + +/** + * 发布审计事件 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/8/1 21:04 + */ +@Component +@AllArgsConstructor +public class AuditEventPublish { + + private final Logger logger = LoggerFactory.getLogger(AuditEventPublish.class); + + /** + * 发布 审计事件 + * + * @param eventType {@link EventType} + */ + public void publish(EventType eventType, String content, EventStatus eventStatus) { + //@formatter:off + //封装操作事件 + Event event = Event.builder() + .type(eventType) + .time(Instant.now()) + .content(content) + .status(eventStatus).build(); + //封装地理位置 + GeoLocation geoLocationModal = getGeoLocation(); + //封装用户代理 + UserAgent userAgent = getUserAgent(); + //封装操作人 + Actor actor = getActor(); + //Publish AuditEvent + applicationEventPublisher.publishEvent(new AuditEvent(TraceUtils.get(), ServletContextHelp.getSession().getId(), actor, event, userAgent, geoLocationModal, null)); + //@formatter:on + } + + /** + * 发布 审计事件 + * + * @param eventType {@link EventType} + */ + public void publish(EventType eventType, Authentication authentication, EventStatus eventStatus, + List targets) { + //@formatter:off + //封装操作事件 + Event event = Event.builder() + .type(eventType) + .time(Instant.now()) + .status(eventStatus).build(); + if (authentication.getPrincipal() instanceof UserDetails){ + String username = ((UserDetails) authentication.getPrincipal()).getUsername(); + event.setContent(username+":"+event.getType().getDesc()); + } + //封装地理位置 + GeoLocation geoLocationModal = getGeoLocation(); + //封装用户代理 + UserAgent userAgent = getUserAgent(); + //封装操作人 + Actor actor = getActor(authentication); + //Publish AuditEvent + applicationEventPublisher.publishEvent(new AuditEvent(TraceUtils.get(), ServletContextHelp.getSession().getId(), actor, event, userAgent, geoLocationModal, targets)); + //@formatter:on + } + + /** + * 发布 审计事件 + * + * @param eventType {@link EventType} + */ + public void publish(EventType eventType, String content, Actor actor, EventStatus eventStatus) { + //@formatter:off + //封装操作事件 + Event event = Event.builder() + .type(eventType) + .time(Instant.now()) + .content(content) + .status(eventStatus).build(); + //封装地理位置 + GeoLocation geoLocationModal = getGeoLocation(); + //封装用户代理 + UserAgent userAgent = getUserAgent(); + //Publish AuditEvent + applicationEventPublisher.publishEvent(new AuditEvent(TraceUtils.get(), ServletContextHelp.getSession().getId(), actor, event, userAgent, geoLocationModal, null)); + //@formatter:on + } + + /** + * 发布 审计事件 + * + * @param eventType {@link EventType} + */ + public void publish(EventType eventType, Map parameters, String content, + List target, String result, EventStatus eventStatus) { + //@formatter:off + //封装操作事件 + Event event = Event.builder() + .type(eventType) + .time(Instant.now()) + .status(eventStatus).build(); + if (!Objects.isNull(parameters)){ + try { + event.setParam(replaceBlank(JSONObject.toJSONString(parameters))); + } catch (Exception e) { + event.setParam(parameters.toString()); + } + } + //描述 + if (StringUtils.isNotBlank(content)){ + event.setContent(content); + } + //事件结果 + if (StringUtils.isNotBlank(result)){ + event.setResult(result); + } + //封装地理位置 + GeoLocation geoLocationModal = getGeoLocation(); + //封装用户代理 + UserAgent userAgent = getUserAgent(); + //封装操作人 + Actor actor = getActor(); + //Publish AuditEvent + applicationEventPublisher.publishEvent(new AuditEvent(TraceUtils.get(), ServletContextHelp.getSession().getId(), actor, event, userAgent, geoLocationModal, target)); + //@formatter:on + } + + /** + * 发布 审计事件 + * + * @param eventType {@link EventType} + */ + public void publish(EventType eventType, List target, String result, + EventStatus eventStatus) { + //@formatter:off + //封装操作事件 + Event event = Event.builder() + .type(eventType) + .time(Instant.now()) + .status(eventStatus).build(); + //事件结果 + event.setResult(result); + //封装地理位置 + GeoLocation geoLocationModal = getGeoLocation(); + //封装用户代理 + UserAgent userAgent = getUserAgent(); + //封装操作人 + Actor actor = getActor(); + //Publish AuditEvent + applicationEventPublisher.publishEvent(new AuditEvent(TraceUtils.get(), ServletContextHelp.getSession().getId(), actor, event, userAgent, geoLocationModal, target)); + //@formatter:on + } + + /** + * 封装操作者 + * @return {@link Actor} + */ + private Actor getActor() { + //@formatter:off + SecurityContext securityContext = SecurityContextHolder.getContext(); + Authentication authentication = securityContext.getAuthentication(); + return Actor.builder() + .id(getActorId(authentication)) + .type(getActorType(authentication)) + .build(); + //@formatter:on + } + + /** + * 封装操作者 + * @param authentication {@link Authentication} + * @return {@link Actor} + */ + private Actor getActor(Authentication authentication) { + //@formatter:off + return Actor.builder() + .id(getActorId(authentication)) + .type(getActorType(authentication)) + .build(); + //@formatter:on + } + + private String getActorId(Authentication authentication) { + //@formatter:off + Object principal = authentication.getPrincipal(); + if (principal instanceof UserDetails) { + return ((UserDetails) principal).getId(); + } + return null; + //@formatter:on + } + + /** + * 获取行动者类型 + * + * @param authentication {@link Authentication} + * @return {@link UserType} + */ + private UserType getActorType(Authentication authentication) { + //@formatter:off + Object principal = authentication.getPrincipal(); + if (principal instanceof UserDetails) { + return ((UserDetails) principal).getUserType(); + } + return null; + //@formatter:on + } + + /** + * 获取用户代理 + * + * @return {@link UserAgent} + */ + private UserAgent getUserAgent() { + //@formatter:off + HttpServletRequest request = ServletContextHelp.getRequest(); + cn.topiam.employee.support.web.useragent.UserAgent ua = UserAgentUtils.getUserAgent(request); + return UserAgent.builder() + .browser(ua.getBrowser()) + .browserType(ua.getBrowserType()) + .browserMajorVersion(ua.getBrowserMajorVersion()) + .platform(ua.getPlatform()) + .platformVersion(ua.getPlatformVersion()) + .deviceType(ua.getDeviceType()) + .build(); + //@formatter:on + } + + /** + * 获取地理位置 + * + * @return {@link GeoLocation} + */ + private GeoLocation getGeoLocation() { + //@formatter:off + HttpServletRequest request = ServletContextHelp.getRequest(); + String ip = IpUtils.getIpAddr(request); + cn.topiam.employee.common.geo.GeoLocation geoLocation = geoLocationService.getGeoLocation(ip); + if (Objects.isNull(geoLocation)){ + return null; + } + if (IpUtils.isInternalIp(ip)){ + return GeoLocation.builder() + .ip(geoLocation.getIp()) + .provider(geoLocation.getProvider()) + .build(); + } + GeoPoint geoPoint = null; + if (!Objects.isNull(geoLocation.getLatitude()) && !Objects.isNull(geoLocation.getLongitude())) { + geoPoint = new GeoPoint(geoLocation.getLatitude(), geoLocation.getLongitude()); + } + return GeoLocation.builder() + .ip(ip) + .continentCode(geoLocation.getContinentCode()) + .continentName(geoLocation.getContinentName()) + .countryCode(geoLocation.getCountryCode()) + .countryName(geoLocation.getCountryName()) + .provinceCode(geoLocation.getProvinceCode()) + .provinceName(geoLocation.getProvinceName()) + .cityCode(geoLocation.getCityCode()) + .cityName(geoLocation.getCityName()) + .point(geoPoint) + .provider(geoLocation.getProvider()) + .build(); + //@formatter:on + } + + /** + * ApplicationEventPublisher + */ + private final ApplicationEventPublisher applicationEventPublisher; + /** + * 地理位置 + */ + private final GeoLocationService geoLocationService; + +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/AccountEventType.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/AccountEventType.java new file mode 100644 index 00000000..8f02c2c3 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/AccountEventType.java @@ -0,0 +1,213 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.event.type; + +import java.util.List; + +import cn.topiam.employee.common.enums.UserType; +import static cn.topiam.employee.audit.event.type.Resource.ACCOUNT_RESOURCE; + +/** + * 账户资源 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/24 22:58 + */ +public class AccountEventType { + + /** + * 创建用户 + */ + public static Type CREATE_USER = new Type("eiam:event:account:create_user", + "创建用户", ACCOUNT_RESOURCE, List.of(UserType.ADMIN)); + /** + * 编辑用户 + */ + public static Type UPDATE_USER = new Type("eiam:event:account:update_user", + "修改用户", ACCOUNT_RESOURCE, List.of(UserType.ADMIN)); + /** + * 删除用户 + */ + public static Type DELETE_USER = new Type("eiam:event:account:delete_user", + "删除用户", ACCOUNT_RESOURCE, List.of(UserType.ADMIN)); + /** + * 禁用用户 + */ + public static Type DISABLE_USER = new Type("eiam:event:account:disabled_user", + "禁用用户", ACCOUNT_RESOURCE, List.of(UserType.ADMIN)); + /** + * 启用用户 + */ + public static Type ENABLE_USER = new Type("eiam:event:account:enabled_user", + "启用用户", ACCOUNT_RESOURCE, List.of(UserType.ADMIN)); + /** + * 创建组织 + */ + public static Type CREATE_ORG = new Type( + "eiam:event:account:create_organization", "创建组织", ACCOUNT_RESOURCE, + List.of(UserType.ADMIN)); + /** + * 编辑组织 + */ + public static Type UPDATE_ORG = new Type( + "eiam:event:account:update_organization", "修改组织", ACCOUNT_RESOURCE, + List.of(UserType.ADMIN)); + /** + * 删除组织 + */ + public static Type DELETE_ORGANIZATION = new Type( + "eiam:event:account:delete_organization", "删除组织", ACCOUNT_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 移动组织 + */ + public static Type MOVE_ORGANIZATION = new Type("eiam:event:account:move_organization", + "移动组织", ACCOUNT_RESOURCE, List.of(UserType.ADMIN)); + /** + * 用户转岗到其他组织 + */ + public static Type USER_TRANSFER_ORG = new Type("eiam:event:account:transfer_user", + "用户转岗", ACCOUNT_RESOURCE, List.of(UserType.ADMIN)); + /** + * 从组织中移除用户 + */ + public static Type ORG_REMOVE_USER = new Type( + "eiam:event:account:delete_user_organization", "移除组织用户", ACCOUNT_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 添加用户到组织 + */ + public static Type USER_ADD_ORG = new Type( + "eiam:event:account:user_add_organization", "组织添加用户", ACCOUNT_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 绑定账号 + */ + public static Type BIND_IDP_USER = new Type("eiam:event:account:bind_idp_user", + "绑定 IDP", ACCOUNT_RESOURCE, List.of(UserType.ADMIN, UserType.USER)); + /** + * 解绑账号 + */ + public static Type UNBIND_IDP_USER = new Type("eiam:event:account:unbind_idp_user", + "解绑 IDP", ACCOUNT_RESOURCE, List.of(UserType.ADMIN, UserType.USER)); + /** + * 用户应用解绑 + */ + public static Type UNBIND_APPLICATION_USER = new Type( + "eiam:event:account:unbind_application_user", "解绑应用账户", ACCOUNT_RESOURCE, + List.of(UserType.ADMIN)); + /** + * 用户注册 + */ + public static Type USER_REGISTER = new Type("eiam:event:account:user_register", + "用户注册", ACCOUNT_RESOURCE, List.of(UserType.USER)); + + /** + * 修改账户信息 + */ + public static Type MODIFY_ACCOUNT_INFO = new Type( + "eiam:event:account:update_account_info", "修改账户", ACCOUNT_RESOURCE, + List.of(UserType.ADMIN, UserType.USER)); + /** + * 修改邮箱 + */ + public static Type MODIFY_USER_EMAIL = new Type("eiam:event:account:update_email", + "修改邮箱", ACCOUNT_RESOURCE, List.of(UserType.ADMIN, UserType.USER)); + /** + * 修改手机号 + */ + public static Type MODIFY_USER_PHONE = new Type("eiam:event:account:update_phone", + "修改手机号", ACCOUNT_RESOURCE, List.of(UserType.ADMIN, UserType.USER)); + /** + * 修改密码 + */ + public static Type MODIFY_USER_PASSWORD = new Type("eiam:event:account:update_password", + "修改密码", ACCOUNT_RESOURCE, List.of(UserType.ADMIN, UserType.USER)); + + /** + * 创建用户组 + */ + public static Type CREATE_USER_GROUP = new Type("eiam:event:account:create_user_group", + "创建用户组", ACCOUNT_RESOURCE, List.of(UserType.ADMIN)); + /** + * 编辑用户组 + */ + public static Type UPDATE_USER_GROUP = new Type("eiam:event:account:update_user_group", + "修改用户组", ACCOUNT_RESOURCE, List.of(UserType.ADMIN)); + /** + * 删除用户组 + */ + public static Type DELETE_USER_GROUP = new Type("eiam:event:account:delete_user_group", + "删除用户组", ACCOUNT_RESOURCE, List.of(UserType.ADMIN)); + + /** + * 添加用户组成员 + */ + public static Type ADD_USER_GROUP_MEMBER = new Type( + "eiam:event:account:add_user_group_member", "添加用户组成员", ACCOUNT_RESOURCE, + List.of(UserType.ADMIN)); + /** + * 移除用户组成员 + */ + public static Type REMOVE_USER_GROUP_MEMBER = new Type( + "eiam:event:account:remove_user_group_member", "移除用户组成员", ACCOUNT_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 创建身份源 + */ + public static Type CREATE_IDENTITY_RESOURCE = new Type( + "eiam:event:account:create_identity_resource", "创建身份源", ACCOUNT_RESOURCE, + List.of(UserType.ADMIN)); + /** + * 编辑身份源 + */ + public static Type UPDATE_IDENTITY_RESOURCE = new Type( + "eiam:event:account:update_identity_resource", "修改身份源", ACCOUNT_RESOURCE, + List.of(UserType.ADMIN)); + /** + * 删除身份源 + */ + public static Type DELETE_IDENTITY_RESOURCE = new Type( + "eiam:event:account:delete_identity_resource", "删除身份源", ACCOUNT_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 启用身份源 + */ + public static Type ENABLE_IDENTITY_RESOURCE = new Type( + "eiam:event:account:enable_identity_resource", "启用身份源", ACCOUNT_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 禁用身份源 + */ + public static Type DISABLE_IDENTITY_RESOURCE = new Type( + "eiam:event:account:disable_identity_resource", "禁用身份源", ACCOUNT_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 禁用身份源 + */ + public static Type IDENTITY_RESOURCE_SYNC = new Type( + "eiam:event:account:identity_resource_sync", "身份源同步", ACCOUNT_RESOURCE, + List.of(UserType.ADMIN)); +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/AppEventType.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/AppEventType.java new file mode 100644 index 00000000..5b1c8c9f --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/AppEventType.java @@ -0,0 +1,165 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.event.type; + +import java.util.List; + +import cn.topiam.employee.common.enums.UserType; +import static cn.topiam.employee.audit.event.type.Resource.APP_RESOURCE; + +/** + * 应用资源 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/24 23:00 + */ +public class AppEventType { + /** + * 单点登录 + */ + public static Type APP_SSO = new Type("eiam:event:app:sso", "单点登录", + APP_RESOURCE, List.of(UserType.USER)); + /** + * 退出应用 + */ + public static Type SIGN_OUT_APP = new Type("eiam:event:app:login", "退出应用", + APP_RESOURCE, List.of(UserType.USER)); + /** + * 添加应用 + */ + public static Type ADD_APP = new Type("eiam:event:app:create", "添加应用", + APP_RESOURCE, List.of(UserType.ADMIN)); + /** + * 启用应用 + */ + public static Type ENABLE_APP = new Type("eiam:event:app:enabled", "启用应用", + APP_RESOURCE, List.of(UserType.ADMIN)); + /** + * 禁用应用 + */ + public static Type DISABLE_APP = new Type("eiam:event:app:disabled", "禁用应用", + APP_RESOURCE, List.of(UserType.ADMIN)); + /** + * 编辑应用 + */ + public static Type UPDATE_APP = new Type("eiam:event:app:update", "修改应用", + APP_RESOURCE, List.of(UserType.ADMIN)); + /** + * 保存应用配置 + */ + public static Type SAVE_APP_CONFIG = new Type("eiam:event:app:save:config", + "保存应用配置", APP_RESOURCE, List.of(UserType.ADMIN)); + + /** + * 删除应用 + */ + public static Type DELETE_APP = new Type("eiam:event:app:delete", "删除应用", + APP_RESOURCE, List.of(UserType.ADMIN)); + /** + * 应用访问授权 + */ + public static Type APP_ACCESS_POLICY = new Type("eiam:event:app:access_policy", + "应用访问授权", APP_RESOURCE, List.of(UserType.ADMIN)); + + /** + * 删除访问授权 + */ + public static Type APP_DELETE_ACCESS_POLICY = new Type( + "eiam:event:app:delete_access_policy", "应用访问授权", APP_RESOURCE, List.of(UserType.ADMIN)); + + /** + * 添加应用账户 + */ + public static Type ADD_APP_ACCOUNT = new Type("eiam:event:app:add_app_account", + "添加应用账户", APP_RESOURCE, List.of(UserType.ADMIN)); + + /** + * 删除应用账户 + */ + public static Type DELETE_APP_ACCOUNT = new Type( + "eiam:event:app:delete_app_account", "删除应用账户", APP_RESOURCE, List.of(UserType.ADMIN)); + + /** + * 保存SSO配置 + */ + public static Type SAVE_SSO_CONFIG = new Type("eiam:event:app:save_sso_config", + "保存SSO配置", APP_RESOURCE, List.of(UserType.ADMIN)); + + /** + * 保存应用权限资源 + */ + public static Type SAVE_APP_PERMISSION_RESOURCE = new Type( + "eiam:event:app:save_app_permission_resource", "保存应用权限资源", APP_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 修改应用权限资源 + */ + public static Type UPDATE_APP_PERMISSION_RESOURCE = new Type( + "eiam:event:app:update_app_permission_resource", "修改应用权限资源", APP_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 删除应用权限资源 + */ + public static Type DELETE_APP_PERMISSION_RESOURCE = new Type( + "eiam:event:app:delete_app_permission_resource", "删除应用权限资源", APP_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 启用应用权限资源 + */ + public static Type ENABLE_APP_PERMISSION_RESOURCE = new Type( + "eiam:event:app:enable_app_permission_resource", "启用应用权限资源", APP_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 禁用应用权限资源 + */ + public static Type DISABLE_APP_PERMISSION_RESOURCE = new Type( + "eiam:event:app:disable_app_permission_resource", "禁用应用权限资源", APP_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 删除用户身份提供商绑定 + */ + public static Type DELETE_USER_IDP_BIND = new Type( + "eiam:event:app:delete_user_idp_bind", "删除用户身份提供商绑定", APP_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 添加应用角色 + */ + public static Type SAVE_APP_PERMISSION_ROLE = new Type( + "eiam:event:app:save_app_permission_role", "添加应用角色", APP_RESOURCE, List.of(UserType.ADMIN)); + + /** + * 修改应用角色 + */ + public static Type UPDATE_APP_PERMISSION_ROLE = new Type( + "eiam:event:app:update_app_permission_role", "修改应用角色", APP_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 删除应用角色 + */ + public static Type DELETE_APP_PERMISSION_ROLE = new Type( + "eiam:event:app:delete_app_permission_role", "删除应用角色", APP_RESOURCE, + List.of(UserType.ADMIN)); + +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/AuthenticationEventType.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/AuthenticationEventType.java new file mode 100644 index 00000000..111eedd5 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/AuthenticationEventType.java @@ -0,0 +1,82 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.event.type; + +import java.util.List; + +import cn.topiam.employee.common.enums.UserType; +import static cn.topiam.employee.audit.event.type.Resource.AUTHENTICATION_RESOURCE; + +/** + * 认证资源 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/24 22:59 + */ +public class AuthenticationEventType { + + /** + * 添加身份提供商 + */ + public static Type ADD_IDP = new Type("eiam:event:idp_add", "添加身份提供商", + AUTHENTICATION_RESOURCE, List.of(UserType.ADMIN)); + /** + * 编辑身份提供商 + */ + public static Type UPDATE_IDP = new Type("eiam:event:idp_update", "修改身份提供商", + AUTHENTICATION_RESOURCE, List.of(UserType.ADMIN)); + /** + * 启用身份提供商 + */ + public static Type ENABLE_IDP = new Type("eiam:event:idp_enabled", "启用身份提供商", + AUTHENTICATION_RESOURCE, List.of(UserType.ADMIN)); + /** + * 禁用身份提供商 + */ + public static Type DISABLE_IDP = new Type("eiam:event:idp_disabled", "禁用身份提供商", + AUTHENTICATION_RESOURCE, List.of(UserType.ADMIN)); + /** + * 删除身份提供商 + */ + public static Type DELETE_IDP = new Type("eiam:event:idp_delete", "删除身份提供商", + AUTHENTICATION_RESOURCE, List.of(UserType.ADMIN)); + + /** + * 登录控制台 + */ + public static Type LOGIN_CONSOLE = new Type("eiam:event:login:console", "登录控制台", + AUTHENTICATION_RESOURCE, List.of(UserType.ADMIN, UserType.USER)); + + /** + * 登录门户 + */ + public static Type LOGIN_PORTAL = new Type("eiam:event:login:portal", "登录门户", + AUTHENTICATION_RESOURCE, List.of(UserType.ADMIN, UserType.USER)); + + /** + * 退出控制台 + */ + public static Type LOGOUT_CONSOLE = new Type("eiam:event:logout:console", "退出控制台", + AUTHENTICATION_RESOURCE, List.of(UserType.ADMIN, UserType.USER)); + + /** + * 退出门户 + */ + public static Type LOGOUT_PORTAL = new Type("eiam:event:logout:portal", "退出门户", + AUTHENTICATION_RESOURCE, List.of(UserType.ADMIN, UserType.USER)); +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/OtherEventType.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/OtherEventType.java new file mode 100644 index 00000000..b8b557a7 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/OtherEventType.java @@ -0,0 +1,46 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.event.type; + +import java.util.List; + +import cn.topiam.employee.common.enums.UserType; +import static cn.topiam.employee.audit.event.type.Resource.OTHER_RESOURCE; + +/** + * 其他设置 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/24 22:58 + */ +public class OtherEventType { + + /** + * 下线会话 + */ + public static Type DOWN_LINE_SESSION = new Type("eiam:event:other:down_line_session", + "下线会话", OTHER_RESOURCE, List.of(UserType.ADMIN)); + + /** + * 批量下线会话 + */ + public static Type BATCH_DOWN_LINE_SESSION = new Type( + "eiam:event:other:batch_down_line_session", "批量下线会话", OTHER_RESOURCE, + List.of(UserType.ADMIN)); + +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/Resource.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/Resource.java new file mode 100644 index 00000000..0340c173 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/Resource.java @@ -0,0 +1,75 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.event.type; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * 审计资源 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/29 21:07 + */ +@Data +@AllArgsConstructor +public class Resource { + /** + * 资源编码 + */ + private String code; + /** + * 资源名称 + */ + private String name; + + @Override + public String toString() { + return String.format("[%s](%s)", name, code); + } + + /** + * 账户 + */ + public static Resource ACCOUNT_RESOURCE = new Resource("eiam:event:resource:account", + "账户管理"); + + /** + * 认证 + */ + public static Resource AUTHENTICATION_RESOURCE = new Resource( + "eiam:event:resource:authentication", "认证管理"); + /** + * 应用 + */ + public static Resource APP_RESOURCE = new Resource("eiam:event:resource:application", + "应用管理"); + + /** + * 其他管理 + */ + public static Resource OTHER_RESOURCE = new Resource("eiam:event:resource:other", + "其他管理"); + + /** + * 系统设置 + */ + public static Resource SETTING_RESOURCE = new Resource("eiam:event:resource:settings", + "系统设置"); + +} \ No newline at end of file diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/SettingEventType.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/SettingEventType.java new file mode 100644 index 00000000..912ea646 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/SettingEventType.java @@ -0,0 +1,314 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.event.type; + +import java.util.List; + +import cn.topiam.employee.common.enums.UserType; +import static cn.topiam.employee.audit.event.type.Resource.SETTING_RESOURCE; + +/** + * 系统设置 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/24 22:58 + */ +public class SettingEventType { + + /** + * 保存安全基础设置 + */ + public static Type SAVE_LOGIN_SECURITY_BASIC_SETTINGS = new Type( + "eiam:event:setting:save_security_basic", "保存安全基础设置", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 密码策略 + */ + public static Type SAVE_PASSWORD_POLICY_SETTINGS = new Type( + "eiam:event:setting:save_password_policy", "保存密码策略", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 保存多因素认证 + */ + public static Type SAVE_MFA_SETTINGS = new Type( + "eiam:event:setting:save_mfa", "保存多因素认证", SETTING_RESOURCE, List.of(UserType.ADMIN)); + + /** + * 保存行为验证码 + */ + public static Type SAVE_CAPTCHA_PROVIDER = new Type( + "eiam:event:setting:save_captcha_provider", "保存行为验证码", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 保存行为验证码 + */ + public static Type OFF_CAPTCHA_PROVIDER = new Type( + "eiam:event:setting:off_captcha_provider", "禁用行为验证码", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 添加管理员 + */ + public static Type ADD_ADMINISTRATOR = new Type( + "eiam:event:setting:add_administrator", "添加管理员", SETTING_RESOURCE, List.of(UserType.ADMIN)); + + /** + * 删除管理员 + */ + public static Type DELETE_ADMINISTRATOR = new Type( + "eiam:event:setting:delete_administrator", "删除管理员", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 修改管理员 + */ + public static Type UPDATE_ADMINISTRATOR = new Type( + "eiam:event:setting:update_administrator", "修改管理员", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 启用管理员 + */ + public static Type ENABLE_ADMINISTRATOR = new Type( + "eiam:event:setting:enable_administrator", "启用管理员", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 禁用管理员 + */ + public static Type DISABLE_ADMINISTRATOR = new Type( + "eiam:event:setting:disable_administrator", "禁用管理员", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + /** + * 重置管理员密码 + */ + public static Type RESET_ADMINISTRATOR_PASSWORD = new Type( + "eiam:event:setting:reset_administrator_password", "重置管理员密码", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 开启自定义修改密码邮件模板 + */ + public static Type ON_CUSTOMIZE_CHANGE_PASSWORD_MAIL = new Type( + "eiam:event:setting:on_customize_change_password_mail", "开启自定义修改密码邮件模板", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 开启自定义重置密码邮件模板 + */ + public static Type ON_CUSTOMIZE_RESET_PASSWORD_MAIL = new Type( + "eiam:event:setting:on_customize_reset_password_mail", "开启自定义重置密码邮件模板", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 开启自定义确认重置密码邮件模板 + */ + public static Type ON_CUSTOMIZE_CONFIRM_RESET_PASSWORD_MAIL = new Type( + "eiam:event:setting:on_customize_confirm_reset_password_mail", "开启自定义确认重置密码邮件模板", + SETTING_RESOURCE, List.of(UserType.ADMIN)); + + /** + * 开启自定义验证邮件模板 + */ + public static Type ON_CUSTOMIZE_VERIFY_MAIL = new Type( + "eiam:event:setting:on_customize_verify_mail", "开启自定义验证邮件模板", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 开启自定义欢迎邮件模板 + */ + public static Type ON_CUSTOMIZE_WELCOME_MAIL = new Type( + "eiam:event:setting:on_customize_welcome_mail", "开启自定义欢迎邮件模板", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 开启自定义修改绑定邮件模板 + */ + public static Type ON_CUSTOMIZE_MODIFY_BINDING_MAIL = new Type( + "eiam:event:setting:on_customize_modify_binding:_mail", "开启自定义修改绑定邮件模板", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 关闭自定义修改密码邮件模板 + */ + public static Type OFF_CUSTOMIZE_CHANGE_PASSWORD_MAIL = new Type( + "eiam:event:setting:off_customize_change_password_mail", "关闭自定义修改密码邮件模板", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 关闭自定义重置密码邮件模板 + */ + public static Type OFF_CUSTOMIZE_RESET_PASSWORD_MAIL = new Type( + "eiam:event:setting:off_customize_reset_password_mail", "关闭自定义重置密码邮件模板", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 关闭自定义确认重置密码邮件模板 + */ + public static Type OFF_CUSTOMIZE_CONFIRM_RESET_PASSWORD_MAIL = new Type( + "eiam:event:setting:off_customize_confirm_reset_password_mail", "关闭自定义确认重置密码邮件模板", + SETTING_RESOURCE, List.of(UserType.ADMIN)); + + /** + * 关闭自定义验证邮件模板 + */ + public static Type OFF_CUSTOMIZE_VERIFY_MAIL = new Type( + "eiam:event:setting:off_customize_verify_mail", "关闭自定义验证邮件模板", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 关闭自定义欢迎邮件模板 + */ + public static Type OFF_CUSTOMIZE_WELCOME_MAIL = new Type( + "eiam:event:setting:off_customize_welcome_mail", "关闭自定义欢迎邮件模板", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 关闭自定义修改绑定邮件模板 + */ + public static Type OFF_CUSTOMIZE_MODIFY_BINDING_MAIL = new Type( + "eiam:event:setting:off_customize_modify_binding_mail", "关闭自定义修改绑定邮件模板", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 保存自定义修改密码邮件模板 + */ + public static Type SAVE_CUSTOMIZE_CHANGE_PASSWORD_MAIL = new Type( + "eiam:event:setting:save_customize_change_password_mail", "保存自定义修改密码邮件模板", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 保存自定义重置密码邮件模板 + */ + public static Type SAVE_CUSTOMIZE_RESET_PASSWORD_MAIL = new Type( + "eiam:event:setting:save_customize_reset_password_mail", "保存自定义重置密码邮件模板", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 保存自定义确认重置密码邮件模板 + */ + public static Type SAVE_CUSTOMIZE_CONFIRM_RESET_PASSWORD_MAIL = new Type( + "eiam:event:setting:save_customize_confirm_reset_password_mail", "保存自定义确认重置密码邮件模板", + SETTING_RESOURCE, List.of(UserType.ADMIN)); + + /** + * 保存自定义验证邮件模板 + */ + public static Type SAVE_CUSTOMIZE_VERIFY_MAIL = new Type( + "eiam:event:setting:save_customize_verify_mail", "保存自定义验证邮件模板", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 保存自定义欢迎邮件模板 + */ + public static Type SAVE_CUSTOMIZE_WELCOME_MAIL = new Type( + "eiam:event:setting:save_customize_welcome_mail", "保存自定义欢迎邮件模板", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 保存自定义修改绑定邮件模板 + */ + public static Type SAVE_CUSTOMIZE_MODIFY_BINDING_MAIL = new Type( + "eiam:event:setting:save_customize_modify_binding_mail", "保存自定义修改绑定邮件模板", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 开启邮件服务 + */ + public static Type ON_MAIL_SERVICE = new Type( + "eiam:event:setting:on_mail_service", "开启邮件服务", SETTING_RESOURCE, List.of(UserType.ADMIN)); + + /** + * 关闭邮件服务 + */ + public static Type OFF_MAIL_SERVICE = new Type( + "eiam:event:setting:off_mail_service", "关闭邮件服务", SETTING_RESOURCE, List.of(UserType.ADMIN)); + + /** + * 保存邮件服务 + */ + public static Type SAVE_MAIL_SERVICE = new Type( + "eiam:event:setting:save_mail_service", "保存邮件服务", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 开启短信验证服务 + */ + public static Type ON_SMS_SERVICE = new Type( + "eiam:event:setting:on_sms_verify_service", "开启短信验证服务", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 关闭短信验证服务 + */ + public static Type OFF_SMS_SERVICE = new Type( + "eiam:event:setting:off_sms_verify_service", "关闭短信验证服务", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 保存短信验证服务 + */ + public static Type SAVE_SMS_SERVICE = new Type( + "eiam:event:setting:save_sms_verify_service", "保存短信验证服务", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 开启存储服务 + */ + public static Type ON_STORAGE_SERVICE = new Type( + "eiam:event:setting:on_storage_service", "开启存储服务", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 关闭存储服务 + */ + public static Type OFF_STORAGE_SERVICE = new Type( + "eiam:event:setting:off_storage_service", "关闭存储服务", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 保存存储服务 + */ + public static Type SAVE_STORAGE_SERVICE = new Type( + "eiam:event:setting:save_storage_service", "保存存储服务", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 开启地理位置服务 + */ + public static Type ON_GEO_LOCATION_SERVICE = new Type( + "eiam:event:setting:on_geoip_service", "开启地理位置服务", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 关闭地理位置服务 + */ + public static Type OFF_GEO_LOCATION_SERVICE = new Type( + "eiam:event:setting:off_geoip_service", "关闭地理位置服务", SETTING_RESOURCE, + List.of(UserType.ADMIN)); + + /** + * 保存地理位置服务 + */ + public static Type SAVE_GEO_LOCATION_SERVICE = new Type( + "eiam:event:setting:save_geoip_service", "保存地理位置服务", SETTING_RESOURCE, + List.of(UserType.ADMIN)); +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/Type.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/Type.java new file mode 100644 index 00000000..5eea013e --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/event/type/Type.java @@ -0,0 +1,58 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.event.type; + +import java.util.List; + +import cn.topiam.employee.common.enums.UserType; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * 类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/24 23:06 + */ +@Data +@AllArgsConstructor +public class Type { + /** + * 编码 + */ + private String code; + /** + * 名称 + */ + private String name; + /** + * 资源 + */ + private Resource resource; + /** + * 用户类型 + */ + private List userTypes; + + @Override + public String toString() { + return String.format("[%s](%s) %s", name, code, resource); + } + +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/package-info.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/package-info.java new file mode 100644 index 00000000..7f43f787 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/package-info.java @@ -0,0 +1,24 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +/** + * 审计事件 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/11 22:10 + */ +package cn.topiam.employee.audit; \ No newline at end of file diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/repository/AuditRepository.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/repository/AuditRepository.java new file mode 100644 index 00000000..bb6bad44 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/repository/AuditRepository.java @@ -0,0 +1,52 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.repository; + +import java.time.LocalDateTime; + +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.audit.entity.AuditEntity; + +/** + * 行为审计repository + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/11 22:32 + */ +@Repository +public interface AuditRepository extends CrudRepository, + QuerydslPredicateExecutor { + + /** + * 统计指定时间范围内用户登录失败次数 + * + * @param startTime {@link LocalDateTime} + * @param endTime {@link LocalDateTime} + * @param userId {@link Long} + * @return {@link Integer} + */ + @Query(value = "SELECT count(*) FROM `audit` WHERE event_time BETWEEN :startTime AND :endTime AND actor_id = :userId AND event_type = 'eiam:event:account:user_login_fail'", nativeQuery = true) + Integer countLoginFailByUserId(@Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime, + @Param("userId") Long userId); +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/repository/package-info.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/repository/package-info.java new file mode 100644 index 00000000..a1b65766 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/repository/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.repository; \ No newline at end of file diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/service/AuditService.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/service/AuditService.java new file mode 100644 index 00000000..809ee900 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/service/AuditService.java @@ -0,0 +1,52 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.service; + +import java.util.List; + +import cn.topiam.employee.audit.controller.pojo.AuditDictResult; +import cn.topiam.employee.audit.controller.pojo.AuditListQuery; +import cn.topiam.employee.audit.controller.pojo.AuditListResult; +import cn.topiam.employee.common.enums.UserType; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; + +/** + * 审计service + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/10 23:06 + */ +public interface AuditService { + /** + * List + * + * @param query {@link AuditListQuery} + * @param pageModel {@link PageModel} + * @return {@link Page} + */ + Page getAuditList(AuditListQuery query, PageModel pageModel); + + /** + * 获取字典类型 + * + * @param userType {@link UserType} + * @return {@link List} + */ + List getAuditDict(UserType userType); +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/service/converter/AuditDataConverter.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/service/converter/AuditDataConverter.java new file mode 100644 index 00000000..6269b6f7 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/service/converter/AuditDataConverter.java @@ -0,0 +1,204 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.service.converter; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.*; +import java.util.stream.Collectors; + +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.search.sort.FieldSortBuilder; +import org.elasticsearch.search.sort.SortBuilder; +import org.elasticsearch.search.sort.SortBuilders; +import org.elasticsearch.search.sort.SortOrder; +import org.mapstruct.Mapper; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import com.google.common.collect.Lists; + +import cn.topiam.employee.audit.controller.pojo.AuditListQuery; +import cn.topiam.employee.audit.controller.pojo.AuditListResult; +import cn.topiam.employee.audit.entity.*; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.common.entity.account.UserEntity; +import cn.topiam.employee.common.entity.setting.AdministratorEntity; +import cn.topiam.employee.common.enums.UserType; +import cn.topiam.employee.common.repository.account.UserRepository; +import cn.topiam.employee.common.repository.setting.AdministratorRepository; +import cn.topiam.employee.support.context.ApplicationContextHelp; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import static cn.topiam.employee.audit.entity.Actor.ACTOR_ID; +import static cn.topiam.employee.audit.entity.Actor.ACTOR_TYPE; +import static cn.topiam.employee.audit.entity.Event.EVENT_TIME; +import static cn.topiam.employee.audit.entity.Event.EVENT_TYPE; + +/** + * 审计数据转换 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/14 22:45 + */ +@Mapper(componentModel = "spring") +public interface AuditDataConverter { + String SORT_EVENT_TIME = "eventTime"; + + /** + * searchHits 转审计列表 + * + * @param search {@link SearchHits} + * @param page {@link PageModel} + * @return {@link Page} + */ + default Page searchHitsConvertToAuditListResult(SearchHits search, + PageModel page) { + List list = new ArrayList<>(); + //总记录数 + search.forEach(hit -> { + AuditElasticSearchEntity content = hit.getContent(); + Event event = content.getEvent(); + AuditListResult result = new AuditListResult(); + result.setId(content.getId()); + result.setEventStatus(event.getStatus()); + result.setEventType(event.getType().getDesc()); + result.setEventTime(LocalDateTime.ofInstant(event.getTime(), ZoneId.systemDefault())); + //用户代理 + result.setUserAgent(content.getUserAgent()); + result.setGeoLocation(content.getGeoLocation()); + Actor actor = content.getActor(); + //用户ID + result.setUserId(actor.getId()); + result.setUsername(getUsername(actor.getType(), actor.getId())); + //用户类型 + result.setUserType(actor.getType().getCode()); + //操作对象 + result.setTargets(content.getTargets()); + list.add(result); + }); + //@formatter:off + Page result = new Page<>(); + result.setPagination(Page.Pagination.builder() + .total(search.getTotalHits()) + .totalPages(Math.toIntExact(search.getTotalHits() / page.getPageSize())) + .current(page.getCurrent()+1) + .build()); + result.setList(list); + //@formatter:on + return result; + } + + /** + * + * 获取用户名 + * + * @param actorId {@link String} + * @param actorType {@link UserType} + * @return {@link String} + */ + private String getUsername(UserType actorType, String actorId) { + if (!StringUtils.hasText(actorId)) { + return null; + } + if (UserType.USER.equals(actorType)) { + UserRepository repository = ApplicationContextHelp.getBean(UserRepository.class); + UserEntity user = repository.findById(Long.valueOf(actorId)).orElse(new UserEntity()); + return user.getUsername(); + } + if (UserType.ADMIN.equals(actorType)) { + AdministratorRepository repository = ApplicationContextHelp + .getBean(AdministratorRepository.class); + AdministratorEntity administrator = repository.findById(Long.valueOf(actorId)) + .orElse(new AdministratorEntity()); + return administrator.getUsername(); + } + return ""; + } + + /** + * 审计列表请求到本机搜索查询 + * + * @param query {@link AuditListQuery} + * @param page {@link PageModel} + * @return {@link NativeSearchQuery} + */ + default NativeSearchQuery auditListRequestConvertToNativeSearchQuery(AuditListQuery query, + PageModel page) { + //构建查询 builder下有 must、should 以及 mustNot 相当于 sql 中的 and、or 以及 not + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + Collection> fieldSortBuilders = Lists.newArrayList(); + //用户名存在,查询用户ID + if (StringUtils.hasText(query.getUsername())) { + String actorId = ""; + if (UserType.USER.equals(query.getUserType())) { + UserRepository userRepository = ApplicationContextHelp + .getBean(UserRepository.class); + UserEntity user = userRepository.findByUsername(query.getUsername()); + if (!Objects.isNull(user)) { + actorId = user.getId().toString(); + } + } + if (UserType.ADMIN.equals(query.getUserType())) { + AdministratorRepository administratorRepository = ApplicationContextHelp + .getBean(AdministratorRepository.class); + Optional optional = administratorRepository + .findByUsername(query.getUsername()); + if (optional.isPresent()) { + actorId = optional.get().getId().toString(); + } + } + queryBuilder.must(QueryBuilders.queryStringQuery(actorId).field(ACTOR_ID)); + } + //用户类型 + queryBuilder + .must(QueryBuilders.queryStringQuery(query.getUserType().getCode()).field(ACTOR_TYPE)); + //事件类型 + if (!CollectionUtils.isEmpty(query.getEventType())) { + queryBuilder.must(QueryBuilders.termsQuery(EVENT_TYPE, + query.getEventType().stream().map(EventType::getCode).collect(Collectors.toSet()))); + } + //字段排序 + page.getSorts().forEach(sort -> { + FieldSortBuilder eventTimeSortBuilder = SortBuilders.fieldSort(EVENT_TIME) + .order(SortOrder.DESC); + if (org.apache.commons.lang3.StringUtils.equals(sort.getSorter(), SORT_EVENT_TIME)) { + if (sort.getAsc()) { + eventTimeSortBuilder.order(SortOrder.ASC); + } + } + fieldSortBuilders.add(eventTimeSortBuilder); + }); + //事件时间 + if (!Objects.isNull(query.getStartEventTime()) + && !Objects.isNull(query.getEndEventTime())) { + queryBuilder.must(QueryBuilders.rangeQuery(EVENT_TIME).gte(query.getStartEventTime()) + .lte(query.getEndEventTime())); + } + return new NativeSearchQueryBuilder().withQuery(queryBuilder) + //分页参数 + .withPageable(PageRequest.of(page.getCurrent(), page.getPageSize())) + //排序 + .withSorts(fieldSortBuilders).build(); + } +} diff --git a/eiam-audit/src/main/java/cn/topiam/employee/audit/service/impl/AuditServiceImpl.java b/eiam-audit/src/main/java/cn/topiam/employee/audit/service/impl/AuditServiceImpl.java new file mode 100644 index 00000000..e5d840f5 --- /dev/null +++ b/eiam-audit/src/main/java/cn/topiam/employee/audit/service/impl/AuditServiceImpl.java @@ -0,0 +1,127 @@ +/* + * eiam-audit - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.audit.service.impl; + +import java.util.*; +import java.util.stream.Collectors; + +import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; +import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import cn.topiam.employee.audit.controller.pojo.AuditDictResult; +import cn.topiam.employee.audit.controller.pojo.AuditListQuery; +import cn.topiam.employee.audit.controller.pojo.AuditListResult; +import cn.topiam.employee.audit.entity.AuditElasticSearchEntity; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.audit.service.AuditService; +import cn.topiam.employee.audit.service.converter.AuditDataConverter; +import cn.topiam.employee.common.enums.UserType; +import cn.topiam.employee.core.configuration.EiamSupportProperties; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; + +import lombok.AllArgsConstructor; +import static cn.topiam.employee.common.constants.AuditConstants.getAuditIndexPrefix; + +/** + * 审计 service impl + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/10 23:06 + */ +@Service +@AllArgsConstructor +public class AuditServiceImpl implements AuditService { + + /** + * List + * + * @param query {@link AuditListQuery} + * @param page {@link PageModel} + * @return {@link Page} + */ + @Override + public Page getAuditList(AuditListQuery query, PageModel page) { + //查询入参转查询条件 + NativeSearchQuery nsq = auditDataConverter.auditListRequestConvertToNativeSearchQuery(query, + page); + //查询列表 + SearchHits search = elasticsearchRestTemplate.search(nsq, + AuditElasticSearchEntity.class, IndexCoordinates + .of(getAuditIndexPrefix(eiamSupportProperties.getDemo().isOpen()) + "*")); + //结果转返回结果 + return auditDataConverter.searchHitsConvertToAuditListResult(search, page); + } + + /** + * 获取字典类型 + * + * @param userType {@link UserType} + * @return {@link List} + */ + @Override + public List getAuditDict(UserType userType) { + List types = Arrays.asList(EventType.values()); + //获取分组 + List list = types.stream().map(EventType::getResource).toList().stream() + .distinct().toList().stream().map(resource -> { + AuditDictResult group = new AuditDictResult(); + group.setName(resource.getName()); + group.setCode(resource.getCode()); + return group; + }).collect(Collectors.toList()); + //处理每个分组的审计类型 + list.forEach(dict -> { + Set auditTypes = new HashSet<>(); + types.stream() + .filter(auditType -> auditType.getResource().getCode().equals(dict.getCode())) + .forEach(auditType -> { + if (auditType.getUserTypes().contains(userType)) { + AuditDictResult.AuditType type = new AuditDictResult.AuditType(); + type.setName(auditType.getDesc()); + type.setCode(auditType.getCode()); + auditTypes.add(type); + } + }); + dict.setTypes(auditTypes); + }); + list = list.stream().filter(i -> !CollectionUtils.isEmpty(i.getTypes())) + .collect(Collectors.toList()); + return list; + } + + /** + * EiamSupportProperties + */ + private final EiamSupportProperties eiamSupportProperties; + + /** + * ElasticsearchRestTemplate + */ + private final ElasticsearchRestTemplate elasticsearchRestTemplate; + + /** + * AuditDataConverter + */ + private final AuditDataConverter auditDataConverter; + +} diff --git a/eiam-authentication/eiam-authentication-all/pom.xml b/eiam-authentication/eiam-authentication-all/pom.xml new file mode 100644 index 00000000..5fa56a93 --- /dev/null +++ b/eiam-authentication/eiam-authentication-all/pom.xml @@ -0,0 +1,79 @@ + + + + + eiam-authentication + cn.topiam + 1.0.0-beta1 + ../pom.xml + + jar + 4.0.0 + + eiam-authentication-all + + + + + cn.topiam + eiam-authentication-dingtalk + ${project.version} + + + + cn.topiam + eiam-authentication-feishu + ${project.version} + + + + cn.topiam + eiam-authentication-qq + ${project.version} + + + + cn.topiam + eiam-authentication-wechat + ${project.version} + + + + cn.topiam + eiam-authentication-wechatwork + ${project.version} + + + + cn.topiam + eiam-authentication-sms + ${project.version} + + + + cn.topiam + eiam-authentication-mfa + ${project.version} + + + \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-core/pom.xml b/eiam-authentication/eiam-authentication-core/pom.xml new file mode 100644 index 00000000..8843fea6 --- /dev/null +++ b/eiam-authentication/eiam-authentication-core/pom.xml @@ -0,0 +1,35 @@ + + + + + eiam-authentication + cn.topiam + 1.0.0-beta1 + ../pom.xml + + 4.0.0 + + eiam-authentication-core + jar + + \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/AbstractIdentityProviderService.java b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/AbstractIdentityProviderService.java new file mode 100644 index 00000000..87c9dd50 --- /dev/null +++ b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/AbstractIdentityProviderService.java @@ -0,0 +1,31 @@ +/* + * eiam-authentication-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.common; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * AbstractIdentityProviderService + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/31 22:34 + */ +public abstract class AbstractIdentityProviderService implements IdentityProviderService { + private final Logger logger = LoggerFactory.getLogger(AbstractIdentityProviderService.class); +} diff --git a/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/IdentityProviderService.java b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/IdentityProviderService.java new file mode 100644 index 00000000..a90d64a0 --- /dev/null +++ b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/IdentityProviderService.java @@ -0,0 +1,111 @@ +/* + * eiam-authentication-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.common; + +import java.util.List; +import java.util.Map; + +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.authentication.common.config.IdentityProviderConfig; +import cn.topiam.employee.common.enums.IdentityProviderType; + +/** + * IdentityProviderService + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/20 23:20 + */ +public interface IdentityProviderService { + + /** + * 获取身份提供商标志 + * + * @return {@link String} + */ + String getCode(); + + /** + * 获取身份提供商名称 + * + * @return {@link String} + */ + String getName(); + + /** + * 获取身份提供商描述 + * + * @return {@link String} + */ + String getDescription(); + + /** + * 获取身份提供商类型 + * + * @return {@link IdentityProviderType} + */ + IdentityProviderType getType(); + + /** + * 获取表单Schema + * + * @return {@link Map} + */ + List getFormSchema(); + + /** + * 获取base64图标 + * + * @return {@link String} + */ + String getBase64Icon(); + + /** + * 创建身份提供商 + * + * @param name {@link String} 名称 + * @param remark {@link String} 备注 + * @return {@link String} 身份提供商ID + */ + @Transactional(rollbackFor = Exception.class) + String create(String name, String remark); + + /** + * 删除身份提供商 + * + * @param idpId {@link String} 身份提供商ID + */ + void delete(String idpId); + + /** + * 更新身份提供商配置 + * + * @param idpId {@link String} + * @param config {@link Map} + */ + @Transactional(rollbackFor = Exception.class) + void saveConfig(String idpId, Map config); + + /** + * 获取配置 + * + * @param idpId {@link String} + * @return {@link IdentityProviderConfig} + */ + IdentityProviderConfig getConfig(String idpId); +} diff --git a/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/IdentityProviderServiceLoader.java b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/IdentityProviderServiceLoader.java new file mode 100644 index 00000000..41192873 --- /dev/null +++ b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/IdentityProviderServiceLoader.java @@ -0,0 +1,106 @@ +/* + * eiam-authentication-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.common; + +import java.util.*; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.*; +import org.springframework.context.annotation.Configuration; + +import cn.topiam.employee.authentication.common.exception.IdentityProviderTemplateNotExistException; + +/** + * 身份提供商加载器 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/20 19:08 + */ +@Configuration +public class IdentityProviderServiceLoader implements ApplicationContextAware { + + private final Logger logger = LoggerFactory + .getLogger(IdentityProviderServiceLoader.class); + /** + * 用于保存接口实现类名及对应的类 + */ + private Map loadMap = new HashMap<>( + 16); + /** + * key: code,value:templateImpl + */ + private final Map identityProviderServiceMap = new HashMap<>( + 16); + + /** + * Set the ApplicationContext that this object runs in. + * Normally this call will be used to initialize the object. + *

Invoked after population of normal bean properties but before an init callback such + * as {@link InitializingBean#afterPropertiesSet()} + * or a custom init-method. Invoked after {@link ResourceLoaderAware#setResourceLoader}, + * {@link ApplicationEventPublisherAware#setApplicationEventPublisher} and + * {@link MessageSourceAware}, if applicable. + * + * @param applicationContext the ApplicationContext object to be used by this object + * @throws ApplicationContextException in case of context initialization errors + * @throws BeansException if thrown by application context methods + * @see BeanInitializationException + */ + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + loadMap = applicationContext.getBeansOfType(IdentityProviderService.class); + getIdentityProviderServiceList(); + } + + /** + * 获取身份提供商列表 + * + * @return {@link List} + */ + public Set getIdentityProviderServiceList() { + List values = loadMap.values().stream().toList(); + return new HashSet<>(values); + } + + /** + * 根据CODE获取身份提供商 + * + * @param code {@link String} + * @return {@link List} + */ + public IdentityProviderService getIdentityProviderService(String code) { + IdentityProviderService impl = identityProviderServiceMap.get(code); + if (Objects.isNull(impl)) { + for (IdentityProviderService service : getIdentityProviderServiceList()) { + if (code.equals(service.getCode())) { + identityProviderServiceMap.put(code, service); + return service; + } + } + } + if (Objects.isNull(impl)) { + throw new IdentityProviderTemplateNotExistException(); + } + return impl; + } + +} diff --git a/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/config/IdentityProviderConfig.java b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/config/IdentityProviderConfig.java new file mode 100644 index 00000000..d8491caa --- /dev/null +++ b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/config/IdentityProviderConfig.java @@ -0,0 +1,40 @@ +/* + * eiam-authentication-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.common.config; + +import java.io.Serial; +import java.io.Serializable; + +import lombok.Getter; +import lombok.Setter; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2021/12/9 21:05 + */ +@Getter +@Setter +public class IdentityProviderConfig implements Serializable { + + @Serial + private static final long serialVersionUID = -7863290244166345243L; + /** + * 启用 + */ + private Boolean enabled; +} diff --git a/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/exception/IdentityProviderTemplateNotExistException.java b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/exception/IdentityProviderTemplateNotExistException.java new file mode 100644 index 00000000..43fad033 --- /dev/null +++ b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/exception/IdentityProviderTemplateNotExistException.java @@ -0,0 +1,34 @@ +/* + * eiam-authentication-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.common.exception; + +import cn.topiam.employee.support.exception.TopIamException; +import static org.springframework.http.HttpStatus.BAD_REQUEST; + +/** + * 身份提供商模版不存在 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/7/8 22:49 + */ +public class IdentityProviderTemplateNotExistException extends TopIamException { + + public IdentityProviderTemplateNotExistException() { + super("idp_template_not_exist", "身份提供商模版不存在", BAD_REQUEST); + } +} diff --git a/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/filter/AbstractIdpAuthenticationProcessingFilter.java b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/filter/AbstractIdpAuthenticationProcessingFilter.java new file mode 100644 index 00000000..b92337ec --- /dev/null +++ b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/filter/AbstractIdpAuthenticationProcessingFilter.java @@ -0,0 +1,210 @@ +/* + * eiam-authentication-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.common.filter; + +import java.io.IOException; +import java.util.Optional; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; +import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +import com.alibaba.fastjson2.JSONObject; + +import cn.topiam.employee.authentication.common.modal.IdpUser; +import cn.topiam.employee.authentication.common.service.UserIdpService; +import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity; +import cn.topiam.employee.common.enums.IdentityProviderType; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; +import cn.topiam.employee.core.security.authentication.IdpAuthentication; +import cn.topiam.employee.core.security.userdetails.UserDetails; +import cn.topiam.employee.core.security.util.UserUtils; +import cn.topiam.employee.support.result.ApiRestResult; +import cn.topiam.employee.support.util.HttpResponseUtils; + +import lombok.Getter; + +/** + * 身份验证处理过滤器 + *

+ * 用于处理 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/8 21:49 + */ +public abstract class AbstractIdpAuthenticationProcessingFilter extends + AbstractAuthenticationProcessingFilter { + + /** + * 用户认证 + * + * @param request {@link HttpServletRequest} + * @param response {@link HttpServletResponse} + * @param provider {@link IdentityProviderType} + * @param providerId {@link String} + * @param info {@link JSONObject} + * @return {@link Authentication} + */ + public Authentication attemptAuthentication(HttpServletRequest request, + HttpServletResponse response, + IdentityProviderType provider, String providerId, + IdpUser info) throws IOException { + info.setProviderId(providerId); + info.setProviderType(provider); + //调用接口查询是否已绑定 + if (!userIdpService.checkUserIdpIsAlreadyBind(info.getOpenId(), providerId)) { + logger.debug("【" + providerId + "】用户【" + info.getOpenId() + "】未绑定"); + //是否自动绑定 + if (!userIdpService.isAutoBindUserIdp(providerId)) { + setUserBindSessionContent(request, info); + return new IdpAuthentication(provider.getCode(), providerId); + } + //调用接口进行绑定操作 + info.setProviderId(providerId); + if (!userIdpService.bindUserIdp(UserUtils.getUser().getId().toString(), info)) { + ApiRestResult errorResult = ApiRestResult.err(); + String errMsg = "【" + providerId + "】用户【" + info.getOpenId() + "】绑定失败"; + logger.error(errMsg); + errorResult.message(errMsg); + HttpResponseUtils.flushResponseJson(response, HttpStatus.FORBIDDEN.value(), + errorResult); + return null; + } + logger.debug("【" + providerId + "】用户【" + info.getOpenId() + "】绑定成功"); + //绑定成功,直接走认证认证 + return authenticate(info.getOpenId(), provider, providerId, request); + } + logger.debug("【" + providerId + "】用户【" + info.getOpenId() + "】已绑定"); + //存在绑定更新更新账户信息 + if (!userIdpService.updateUser(info, providerId)) { + ApiRestResult errorResult = ApiRestResult.err(); + logger.error("钉钉扫码登录更新用户信息失败"); + errorResult.message("更新用户信息失败"); + HttpResponseUtils.flushResponseJson(response, HttpStatus.FORBIDDEN.value(), + errorResult); + return null; + } + return authenticate(info.getOpenId(), provider, providerId, request); + } + + /** + * 设置用户绑定session值 + * + * @param request {@link HttpServletRequest} + * @param info {@link IdpUser} + */ + private void setUserBindSessionContent(HttpServletRequest request, IdpUser info) { + request.getSession().setAttribute(TOPIAM_USER_BIND_IDP, JSONObject.toJSONString(info)); + } + + public static final String TOPIAM_USER_BIND_IDP = "TOPIAM_USER_BIND_IDP"; + + /** + * 认证 + * + * @param openId {@link String } + * @param provider {@link IdentityProviderType } + * @param providerId {@link String } + * @param request {@link HttpServletRequest } + * @return {@link Authentication } + */ + public Authentication authenticate(String openId, IdentityProviderType provider, + String providerId, HttpServletRequest request) { + //认证 + UserDetails userDetails = userIdpService.getUserDetails(openId, providerId); + IdpAuthentication token = new IdpAuthentication(userDetails, provider.getCode(), providerId, + true, userDetails.getAuthorities()); + // Allow subclasses to set the "details" property + token.setDetails(this.authenticationDetailsSource.buildDetails(request)); + return token; + } + + public IdentityProviderEntity getIdentityProviderEntity(String providerId) { + Optional optional = getIdentityProviderRepository() + .findByIdAndEnabledIsTrue(Long.valueOf(providerId)); + if (optional.isEmpty()) { + //无效身份提供商 + OAuth2Error oauth2Error = new OAuth2Error(INVALID_IDP); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + return optional.get(); + } + + public OAuth2AuthorizationRequest getOAuth2AuthorizationRequest(HttpServletRequest request, + HttpServletResponse response) { + OAuth2AuthorizationRequest authorizationRequest = getAuthorizationRequestRepository() + .removeAuthorizationRequest(request, response); + if (authorizationRequest == null) { + OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + return authorizationRequest; + } + + public static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter"; + public static final String INVALID_CODE_PARAMETER_ERROR_CODE = "invalid_code_parameter"; + + public static final String AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE = "authorization_request_not_found"; + public static final String GET_USERINFO_ERROR_CODE = "get_userinfo_error_code"; + public static final String AUTH_CODE = "authCode"; + public static final String INVALID_IDP = "invalid_idp"; + public static final String INVALID_IDP_CONFIG = "invalid_idp_config"; + + /** + * 授权请求存储库 + */ + @Getter + private final AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository(); + + /** + * 认证用户详情 + */ + @Getter + private final UserIdpService userIdpService; + + /** + * 身份提供者存储库 + */ + @Getter + private final IdentityProviderRepository identityProviderRepository; + + /** + * Creates a new instance + * + * @param defaultFilterProcessesUrl the {@link String} + * @param userIdpService {@link UserIdpService} + * @param identityProviderRepository {@link IdentityProviderRepository} + */ + protected AbstractIdpAuthenticationProcessingFilter(String defaultFilterProcessesUrl, + UserIdpService userIdpService, + IdentityProviderRepository identityProviderRepository) { + super(new AntPathRequestMatcher(defaultFilterProcessesUrl)); + this.userIdpService = userIdpService; + this.identityProviderRepository = identityProviderRepository; + } + +} diff --git a/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/modal/IdpUser.java b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/modal/IdpUser.java new file mode 100644 index 00000000..fb6efa6d --- /dev/null +++ b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/modal/IdpUser.java @@ -0,0 +1,95 @@ +/* + * eiam-authentication-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.common.modal; + +import java.util.Map; + +import cn.topiam.employee.common.enums.IdentityProviderType; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +/** + * IDP用户信息 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/9/18 20:23 + */ +@Data +@Builder +@AllArgsConstructor +public class IdpUser { + public IdpUser() { + } + + /** + * 账户ID + */ + private String accountId; + + /** + * 个人邮箱 + */ + private String email; + + /** + * 手机号 + */ + private String mobile; + + /** + * 昵称 + */ + private String nickName; + + /** + * 头像url + */ + private String avatarUrl; + + /** + * openId + */ + private String openId; + + /** + * 手机号对应的国家号 + */ + private String stateCode; + + /** + * unionId + */ + public String unionId; + + /** + * providerId + */ + private String providerId; + + /** + * providerType + */ + private IdentityProviderType providerType; + + /** + * 额外配置 + */ + private Map additionalInfo; +} diff --git a/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/package-info.java b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/package-info.java new file mode 100644 index 00000000..e0fb066a --- /dev/null +++ b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-authentication-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.common; \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/service/UserIdpService.java b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/service/UserIdpService.java new file mode 100644 index 00000000..5885d752 --- /dev/null +++ b/eiam-authentication/eiam-authentication-core/src/main/java/cn/topiam/employee/authentication/common/service/UserIdpService.java @@ -0,0 +1,74 @@ +/* + * eiam-authentication-core - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.common.service; + +import cn.topiam.employee.authentication.common.modal.IdpUser; +import cn.topiam.employee.core.security.userdetails.UserDetails; + +/** + * UserIdpService + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/9 21:54 + */ +public interface UserIdpService { + + /** + * 检查用户已绑定对应平台 + * + * @param openId {@link String} + * @param providerId {@link String} + * @return {@link Boolean} + */ + Boolean checkUserIdpIsAlreadyBind(String openId, String providerId); + + /** + * 是否自动绑定 + * + * @param providerId {@link String} 提供商ID + * @return {@link Boolean} + */ + Boolean isAutoBindUserIdp(String providerId); + + /** + * 绑定 + * + * @param accountId {@link String} 账户ID + * @param idpUser {@link IdpUser} 用户信息 + * @return {@link Boolean} + */ + Boolean bindUserIdp(String accountId, IdpUser idpUser); + + /** + * 更新账户信息 + * + * @param idpUser {@link IdpUser} 用户信息 + * @param providerId {@link String} 提供商ID + * @return {@link Boolean} + */ + Boolean updateUser(IdpUser idpUser, String providerId); + + /** + * 获取用户详情 + * + * @param openId {@link String} + * @param providerId {@link String} + * @return {@link UserDetails} + */ + UserDetails getUserDetails(String openId, String providerId); +} diff --git a/eiam-authentication/eiam-authentication-dingtalk/pom.xml b/eiam-authentication/eiam-authentication-dingtalk/pom.xml new file mode 100644 index 00000000..5e081e02 --- /dev/null +++ b/eiam-authentication/eiam-authentication-dingtalk/pom.xml @@ -0,0 +1,43 @@ + + + + + eiam-authentication + cn.topiam + 1.0.0-beta1 + ../pom.xml + + 4.0.0 + + eiam-authentication-dingtalk + jar + + + + cn.topiam + eiam-authentication-core + ${project.version} + + + + \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/DingTalkIdpOauthConfig.java b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/DingTalkIdpOauthConfig.java new file mode 100644 index 00000000..3a4e8eb5 --- /dev/null +++ b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/DingTalkIdpOauthConfig.java @@ -0,0 +1,52 @@ +/* + * eiam-authentication-dingtalk - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.dingtalk; + +import java.io.Serial; + +import javax.validation.constraints.NotBlank; + +import cn.topiam.employee.authentication.common.config.IdentityProviderConfig; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 钉钉Oauth配置参数 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/8 21:36 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class DingTalkIdpOauthConfig extends IdentityProviderConfig { + @Serial + private static final long serialVersionUID = -6850223527422243076L; + + /** + * 应用 key + */ + @NotBlank(message = "应用AppKey不能为空") + private String appKey; + + /** + * 应用 Secret + */ + @NotBlank(message = "应用AppSecret不能为空") + private String appSecret; +} diff --git a/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/DingTalkIdpScanCodeConfig.java b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/DingTalkIdpScanCodeConfig.java new file mode 100644 index 00000000..79e95563 --- /dev/null +++ b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/DingTalkIdpScanCodeConfig.java @@ -0,0 +1,52 @@ +/* + * eiam-authentication-dingtalk - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.dingtalk; + +import java.io.Serial; + +import javax.validation.constraints.NotBlank; + +import cn.topiam.employee.authentication.common.config.IdentityProviderConfig; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 钉钉扫码配置参数 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/8 21:36 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class DingTalkIdpScanCodeConfig extends IdentityProviderConfig { + @Serial + private static final long serialVersionUID = -6850223527422243076L; + + /** + * 应用 key + */ + @NotBlank(message = "应用AppKey不能为空") + private String appKey; + + /** + * 应用 Secret + */ + @NotBlank(message = "应用AppSecret不能为空") + private String appSecret; +} diff --git a/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/configurer/DingtalkOAuth2AuthenticationConfigurer.java b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/configurer/DingtalkOAuth2AuthenticationConfigurer.java new file mode 100644 index 00000000..3f1b7bfb --- /dev/null +++ b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/configurer/DingtalkOAuth2AuthenticationConfigurer.java @@ -0,0 +1,92 @@ +/* + * eiam-authentication-dingtalk - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.dingtalk.configurer; + +import org.springframework.security.config.annotation.web.HttpSecurityBuilder; +import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter; +import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.OrRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; + +import cn.topiam.employee.authentication.common.service.UserIdpService; +import cn.topiam.employee.authentication.dingtalk.filter.DingtalkOAuth2AuthorizationRequestRedirectFilter; +import cn.topiam.employee.authentication.dingtalk.filter.DingtalkOauthAuthenticationFilter; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; + +/** + * 认证配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/10 22:58 + */ +@SuppressWarnings("AlibabaClassNamingShouldBeCamel") +public final class DingtalkOAuth2AuthenticationConfigurer> extends + AbstractAuthenticationFilterConfigurer, DingtalkOauthAuthenticationFilter> { + private final IdentityProviderRepository identityProviderRepository; + private final UserIdpService userIdpService; + + public DingtalkOAuth2AuthenticationConfigurer(IdentityProviderRepository identityProviderRepository, + UserIdpService userIdpService) { + Assert.notNull(identityProviderRepository, "identityProviderRepository must not be null"); + Assert.notNull(userIdpService, "userIdpService must not be null"); + this.identityProviderRepository = identityProviderRepository; + this.userIdpService = userIdpService; + } + + /** + * Create the {@link RequestMatcher} given a loginProcessingUrl + * + * @param loginProcessingUrl creates the {@link RequestMatcher} based upon the + * loginProcessingUrl + * @return the {@link RequestMatcher} to use based upon the loginProcessingUrl + */ + @Override + protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) { + return new AntPathRequestMatcher(loginProcessingUrl); + } + + @Override + public void init(H http) throws Exception { + //钉钉登录认证 + DingtalkOauthAuthenticationFilter loginAuthenticationFilter = new DingtalkOauthAuthenticationFilter( + identityProviderRepository, userIdpService); + this.setAuthenticationFilter(loginAuthenticationFilter); + //处理URL + super.loginProcessingUrl(DingtalkOauthAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI); + super.init(http); + } + + @Override + public void configure(H http) throws Exception { + //钉钉请求重定向 + DingtalkOAuth2AuthorizationRequestRedirectFilter requestRedirectFilter = new DingtalkOAuth2AuthorizationRequestRedirectFilter( + identityProviderRepository); + http.addFilterBefore(requestRedirectFilter, OAuth2AuthorizationRequestRedirectFilter.class); + http.addFilterBefore(this.getAuthenticationFilter(), OAuth2LoginAuthenticationFilter.class); + super.configure(http); + } + + public RequestMatcher getRequestMatcher() { + return new OrRequestMatcher( + DingtalkOAuth2AuthorizationRequestRedirectFilter.getRequestMatcher(), + DingtalkOauthAuthenticationFilter.getRequestMatcher()); + } +} diff --git a/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/configurer/DingtalkScanCodeAuthenticationConfigurer.java b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/configurer/DingtalkScanCodeAuthenticationConfigurer.java new file mode 100644 index 00000000..f618a3d2 --- /dev/null +++ b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/configurer/DingtalkScanCodeAuthenticationConfigurer.java @@ -0,0 +1,91 @@ +/* + * eiam-authentication-dingtalk - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.dingtalk.configurer; + +import org.springframework.security.config.annotation.web.HttpSecurityBuilder; +import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter; +import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.OrRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; + +import cn.topiam.employee.authentication.common.service.UserIdpService; +import cn.topiam.employee.authentication.dingtalk.filter.DingtalkScanCodeAuthenticationFilter; +import cn.topiam.employee.authentication.dingtalk.filter.DingtalkScanCodeAuthorizationRequestGetFilter; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; + +/** + * 认证配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/10 22:58 + */ +public final class DingtalkScanCodeAuthenticationConfigurer> + extends + AbstractAuthenticationFilterConfigurer, DingtalkScanCodeAuthenticationFilter> { + private final IdentityProviderRepository identityProviderRepository; + private final UserIdpService userIdpService; + + public DingtalkScanCodeAuthenticationConfigurer(IdentityProviderRepository identityProviderRepository, + UserIdpService userIdpService) { + Assert.notNull(identityProviderRepository, "identityProviderRepository must not be null"); + Assert.notNull(userIdpService, "userIdpService must not be null"); + this.identityProviderRepository = identityProviderRepository; + this.userIdpService = userIdpService; + } + + /** + * Create the {@link RequestMatcher} given a loginProcessingUrl + * + * @param loginProcessingUrl creates the {@link RequestMatcher} based upon the + * loginProcessingUrl + * @return the {@link RequestMatcher} to use based upon the loginProcessingUrl + */ + @Override + protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) { + return new AntPathRequestMatcher(loginProcessingUrl); + } + + @Override + public void init(H http) throws Exception { + //钉钉扫码登录认证 + DingtalkScanCodeAuthenticationFilter loginAuthenticationFilter = new DingtalkScanCodeAuthenticationFilter( + identityProviderRepository, userIdpService); + this.setAuthenticationFilter(loginAuthenticationFilter); + //处理URL + super.loginProcessingUrl(DingtalkScanCodeAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI); + super.init(http); + } + + @Override + public void configure(H http) throws Exception { + //钉钉请求重定向 + DingtalkScanCodeAuthorizationRequestGetFilter requestRedirectFilter = new DingtalkScanCodeAuthorizationRequestGetFilter( + identityProviderRepository); + http.addFilterBefore(requestRedirectFilter, OAuth2AuthorizationRequestRedirectFilter.class); + http.addFilterBefore(this.getAuthenticationFilter(), OAuth2LoginAuthenticationFilter.class); + super.configure(http); + } + + public RequestMatcher getRequestMatcher() { + return new OrRequestMatcher(DingtalkScanCodeAuthenticationFilter.getRequestMatcher(), + DingtalkScanCodeAuthorizationRequestGetFilter.getRequestMatcher()); + } +} diff --git a/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/constant/DingTalkAuthenticationConstants.java b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/constant/DingTalkAuthenticationConstants.java new file mode 100644 index 00000000..365d3d62 --- /dev/null +++ b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/constant/DingTalkAuthenticationConstants.java @@ -0,0 +1,34 @@ +/* + * eiam-authentication-dingtalk - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.dingtalk.constant; + +/** + * 钉钉认证常量 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/9 21:19 + */ +public final class DingTalkAuthenticationConstants { + public static final String APP_ID = "appid"; + public static final String AUTH_CODE = "authCode"; + public static final String GET_USERINFO_BY_CODE = "https://oapi.dingtalk.com/sns/getuserinfo_bycode"; + public static final String SCAN_CODE_URL_AUTHORIZE = "https://oapi.dingtalk.com/connect/oauth2/sns_authorize"; + public static final String URL_AUTHORIZE = "https://login.dingtalk.com/oauth2/auth"; + public static final String GET_USERID_BY_UNIONID = "https://oapi.dingtalk.com/user/getUseridByUnionid"; + public static final String GET_USERINFO_BY_USERID = "https://oapi.dingtalk.com/topapi/v2/user/get"; +} diff --git a/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/filter/DingtalkOAuth2AuthorizationRequestRedirectFilter.java b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/filter/DingtalkOAuth2AuthorizationRequestRedirectFilter.java new file mode 100644 index 00000000..2b7f4312 --- /dev/null +++ b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/filter/DingtalkOAuth2AuthorizationRequestRedirectFilter.java @@ -0,0 +1,144 @@ +/* + * eiam-authentication-dingtalk - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.dingtalk.filter; + +import java.io.IOException; +import java.util.Base64; +import java.util.Map; +import java.util.Optional; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpMethod; +import org.springframework.lang.NonNull; +import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; +import org.springframework.security.crypto.keygen.StringKeyGenerator; +import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; +import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.web.DefaultRedirectStrategy; +import org.springframework.security.web.RedirectStrategy; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; +import org.springframework.web.filter.OncePerRequestFilter; + +import com.alibaba.fastjson2.JSONObject; + +import cn.topiam.employee.authentication.dingtalk.DingTalkIdpOauthConfig; +import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; +import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.RESPONSE_TYPE; + +import static cn.topiam.employee.authentication.dingtalk.constant.DingTalkAuthenticationConstants.URL_AUTHORIZE; +import static cn.topiam.employee.authentication.dingtalk.filter.DingtalkOauthAuthenticationFilter.getLoginUrl; +import static cn.topiam.employee.common.enums.IdentityProviderType.DINGTALK_OAUTH; + +/** + * 微信扫码登录请求重定向过滤器 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/20 20:22 + */ +@SuppressWarnings("ALL") +public class DingtalkOAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilter { + + private final Logger logger = LoggerFactory + .getLogger(DingtalkOAuth2AuthorizationRequestRedirectFilter.class); + + /** + * 提供商ID + */ + public static final String PROVIDER_ID = "providerId"; + + /** + * AntPathRequestMatcher + */ + public static final AntPathRequestMatcher DINGTALK_OAUTH2_REQUEST_MATCHER = new AntPathRequestMatcher( + DINGTALK_OAUTH.getAuthorizationPathPrefix() + "/" + "{" + PROVIDER_ID + "}", + HttpMethod.GET.name()); + + /** + * 重定向策略 + */ + private final RedirectStrategy authorizationRedirectStrategy = new DefaultRedirectStrategy(); + + /** + * 认证请求存储库 + */ + private final AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository(); + + private static final StringKeyGenerator DEFAULT_STATE_GENERATOR = new Base64StringKeyGenerator( + Base64.getUrlEncoder()); + private final IdentityProviderRepository identityProviderRepository; + + public DingtalkOAuth2AuthorizationRequestRedirectFilter(IdentityProviderRepository identityProviderRepository) { + this.identityProviderRepository = identityProviderRepository; + } + + @Override + protected void doFilterInternal(@NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain) throws IOException, + ServletException { + RequestMatcher.MatchResult matcher = DINGTALK_OAUTH2_REQUEST_MATCHER.matcher(request); + if (!matcher.isMatch()) { + filterChain.doFilter(request, response); + return; + } + Map variables = matcher.getVariables(); + String providerId = variables.get(PROVIDER_ID); + Optional optional = identityProviderRepository + .findByIdAndEnabledIsTrue(Long.valueOf(providerId)); + if (optional.isEmpty()) { + throw new NullPointerException("未查询到身份提供商信息"); + } + IdentityProviderEntity entity = optional.get(); + DingTalkIdpOauthConfig config = JSONObject.parseObject(entity.getConfig(), + DingTalkIdpOauthConfig.class); + Assert.notNull(config, "钉钉登录配置不能为空"); + //构建授权请求 + OAuth2AuthorizationRequest.Builder builder = OAuth2AuthorizationRequest.authorizationCode() + .clientId(config.getAppKey()).authorizationUri(URL_AUTHORIZE) + .redirectUri(getLoginUrl(providerId)).state(DEFAULT_STATE_GENERATOR.generateKey()); + builder.parameters(parameters -> { + parameters.put(RESPONSE_TYPE, OAuth2ParameterNames.CODE); + parameters.put("prompt", "consent"); + }); + this.sendRedirectForAuthorization(request, response, builder.build()); + } + + private void sendRedirectForAuthorization(HttpServletRequest request, + HttpServletResponse response, + OAuth2AuthorizationRequest authorizationRequest) throws IOException { + this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, + response); + this.authorizationRedirectStrategy.sendRedirect(request, response, + authorizationRequest.getAuthorizationRequestUri()); + } + + public static RequestMatcher getRequestMatcher() { + return DINGTALK_OAUTH2_REQUEST_MATCHER; + } +} diff --git a/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/filter/DingtalkOauthAuthenticationFilter.java b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/filter/DingtalkOauthAuthenticationFilter.java new file mode 100644 index 00000000..b0d227aa --- /dev/null +++ b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/filter/DingtalkOauthAuthenticationFilter.java @@ -0,0 +1,210 @@ +/* + * eiam-authentication-dingtalk - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.dingtalk.filter; + +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpMethod; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; + +import com.alibaba.fastjson2.JSONObject; +import com.aliyun.dingtalkcontact_1_0.models.GetUserHeaders; +import com.aliyun.dingtalkcontact_1_0.models.GetUserResponse; +import com.aliyun.dingtalkoauth2_1_0.Client; +import com.aliyun.dingtalkoauth2_1_0.models.GetUserTokenRequest; +import com.aliyun.dingtalkoauth2_1_0.models.GetUserTokenResponse; +import com.aliyun.dingtalkoauth2_1_0.models.GetUserTokenResponseBody; +import com.aliyun.teaopenapi.models.Config; +import com.aliyun.teautil.models.RuntimeOptions; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +import cn.topiam.employee.authentication.common.filter.AbstractIdpAuthenticationProcessingFilter; +import cn.topiam.employee.authentication.common.modal.IdpUser; +import cn.topiam.employee.authentication.common.service.UserIdpService; +import cn.topiam.employee.authentication.dingtalk.DingTalkIdpOauthConfig; +import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; +import cn.topiam.employee.core.context.ServerContextHelp; +import cn.topiam.employee.support.exception.TopIamException; +import cn.topiam.employee.support.trace.TraceUtils; +import cn.topiam.employee.support.util.HttpUrlUtils; +import static cn.topiam.employee.authentication.dingtalk.filter.DingtalkScanCodeAuthorizationRequestGetFilter.PROVIDER_ID; +import static cn.topiam.employee.common.enums.IdentityProviderType.DINGTALK_OAUTH; + +/** + * 钉钉认证过滤器 + *

+ * https://open.dingtalk.com/document/orgapp-server/tutorial-obtaining-user-personal-information + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/8 21:11 + */ +@SuppressWarnings("DuplicatedCode") +public class DingtalkOauthAuthenticationFilter extends AbstractIdpAuthenticationProcessingFilter { + public final static String DEFAULT_FILTER_PROCESSES_URI = DINGTALK_OAUTH + .getLoginPathPrefix() + "/*"; + /** + * AntPathRequestMatcher + */ + public static final AntPathRequestMatcher REQUEST_MATCHER = new AntPathRequestMatcher( + DINGTALK_OAUTH.getLoginPathPrefix() + "/" + "{" + PROVIDER_ID + "}", HttpMethod.GET.name()); + + /** + * Creates a new instance + * + * @param identityProviderRepository the {@link IdentityProviderRepository} + * @param authenticationUserDetails {@link UserIdpService} + */ + public DingtalkOauthAuthenticationFilter(IdentityProviderRepository identityProviderRepository, + UserIdpService authenticationUserDetails) { + super(DEFAULT_FILTER_PROCESSES_URI, authenticationUserDetails, identityProviderRepository); + } + + /** + * 钉钉认证 + * + * @param request {@link HttpServletRequest} + * @param response {@link HttpServletRequest} + * @return {@link HttpServletRequest} + * @throws AuthenticationException {@link AuthenticationException} AuthenticationException + */ + @Override + public Authentication attemptAuthentication(HttpServletRequest request, + HttpServletResponse response) throws AuthenticationException, + IOException { + OAuth2AuthorizationRequest authorizationRequest = getOAuth2AuthorizationRequest(request, + response); + TraceUtils.put(UUID.randomUUID().toString()); + RequestMatcher.MatchResult matcher = REQUEST_MATCHER.matcher(request); + Map variables = matcher.getVariables(); + String providerId = variables.get(PROVIDER_ID); + //code 钉钉新版登录为 authCode + String code = request.getParameter(AUTH_CODE); + if (StringUtils.isEmpty(code)) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_CODE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + // state + String state = request.getParameter(OAuth2ParameterNames.STATE); + if (StringUtils.isEmpty(state)) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + //验证state + if (!authorizationRequest.getState().equals(state)) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + //获取身份提供商 + IdentityProviderEntity provider = getIdentityProviderEntity(providerId); + DingTalkIdpOauthConfig idpOauthConfig = JSONObject.parseObject(provider.getConfig(), + DingTalkIdpOauthConfig.class); + if (Objects.isNull(idpOauthConfig)) { + logger.error("未查询到钉钉登录配置"); + //无效身份提供商 + OAuth2Error oauth2Error = new OAuth2Error(INVALID_IDP_CONFIG); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + String accessToken = getToken(code, idpOauthConfig); + Config config = new Config(); + config.protocol = "https"; + config.regionId = "central"; + GetUserHeaders getUserHeaders = new GetUserHeaders(); + getUserHeaders.xAcsDingtalkAccessToken = accessToken; + //获取用户个人信息,如需获取当前授权人的信息,unionId参数必须传me + GetUserResponse user; + try { + com.aliyun.dingtalkcontact_1_0.Client client = new com.aliyun.dingtalkcontact_1_0.Client( + config); + user = client.getUserWithOptions("me", getUserHeaders, new RuntimeOptions()); + } catch (Exception e) { + logger.error("钉钉认证获取用户信息失败: {}", e); + throw new TopIamException("钉钉认证获取用户信息失败", e); + } + //执行逻辑 + IdpUser idpUser = IdpUser.builder().openId(user.getBody().getOpenId()).build(); + return attemptAuthentication(request, response, DINGTALK_OAUTH, providerId, idpUser); + } + + /** + * 获取token + * + * @param authCode {@link String} + * @param config {@link DingTalkIdpOauthConfig} + * @return {@link String} + */ + public String getToken(String authCode, DingTalkIdpOauthConfig config) { + if (!Objects.isNull(cache)) { + cache.getIfPresent(OAuth2ParameterNames.ACCESS_TOKEN); + } + Config clientConfig = new Config(); + clientConfig.protocol = "https"; + clientConfig.regionId = "central"; + try { + Client client = new Client(clientConfig); + GetUserTokenRequest getUserTokenRequest = new GetUserTokenRequest() + //应用基础信息-应用信息的AppKey + .setClientId(config.getAppKey()) + //应用基础信息-应用信息的AppSecret + .setClientSecret(config.getAppSecret()).setCode(authCode) + .setGrantType("authorization_code"); + //获取用户个人token + GetUserTokenResponse getUserTokenResponse = client.getUserToken(getUserTokenRequest); + GetUserTokenResponseBody body = getUserTokenResponse.getBody(); + //放入缓存 + cache = CacheBuilder.newBuilder() + .concurrencyLevel(Runtime.getRuntime().availableProcessors()) + .expireAfterWrite(body.getExpireIn(), TimeUnit.SECONDS).build(); + cache.put(OAuth2ParameterNames.ACCESS_TOKEN, body.getAccessToken()); + return cache.getIfPresent(OAuth2ParameterNames.ACCESS_TOKEN); + } catch (Exception exception) { + throw new RuntimeException(exception.getMessage()); + } + } + + /** + * 缓存 + */ + private Cache cache; + + public static String getLoginUrl(String providerId) { + String url = ServerContextHelp.getPortalPublicBaseUrl() + + DINGTALK_OAUTH.getLoginPathPrefix() + "/" + providerId; + return HttpUrlUtils.format(url); + } + + public static RequestMatcher getRequestMatcher() { + return REQUEST_MATCHER; + } +} diff --git a/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/filter/DingtalkScanCodeAuthenticationFilter.java b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/filter/DingtalkScanCodeAuthenticationFilter.java new file mode 100644 index 00000000..a2df3bad --- /dev/null +++ b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/filter/DingtalkScanCodeAuthenticationFilter.java @@ -0,0 +1,245 @@ +/* + * eiam-authentication-dingtalk - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.dingtalk.filter; + +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpMethod; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; + +import com.alibaba.fastjson2.JSONObject; +import com.aliyun.dingtalkoauth2_1_0.Client; +import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest; +import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponseBody; +import com.aliyun.teaopenapi.models.Config; +import com.dingtalk.api.DefaultDingTalkClient; +import com.dingtalk.api.DingTalkClient; +import com.dingtalk.api.request.OapiSnsGetuserinfoBycodeRequest; +import com.dingtalk.api.request.OapiUserGetUseridByUnionidRequest; +import com.dingtalk.api.request.OapiV2UserGetRequest; +import com.dingtalk.api.response.OapiSnsGetuserinfoBycodeResponse; +import com.dingtalk.api.response.OapiUserGetUseridByUnionidResponse; +import com.dingtalk.api.response.OapiV2UserGetResponse; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.taobao.api.ApiException; + +import cn.topiam.employee.authentication.common.filter.AbstractIdpAuthenticationProcessingFilter; +import cn.topiam.employee.authentication.common.modal.IdpUser; +import cn.topiam.employee.authentication.common.service.UserIdpService; +import cn.topiam.employee.authentication.dingtalk.DingTalkIdpScanCodeConfig; +import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity; +import cn.topiam.employee.common.enums.IdentityProviderType; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; +import cn.topiam.employee.core.context.ServerContextHelp; +import cn.topiam.employee.support.exception.TopIamException; +import cn.topiam.employee.support.trace.TraceUtils; +import cn.topiam.employee.support.util.HttpUrlUtils; +import static cn.topiam.employee.authentication.dingtalk.constant.DingTalkAuthenticationConstants.*; +import static cn.topiam.employee.authentication.dingtalk.filter.DingtalkScanCodeAuthorizationRequestGetFilter.PROVIDER_ID; +import static cn.topiam.employee.common.enums.IdentityProviderType.DINGTALK_SCAN_CODE; + +/** + * 钉钉认证过滤器 + *

+ * https://open.dingtalk.com/document/orgapp-server/scan-qr-code-to-log-on-to-third-party-websites + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/8 21:11 + */ +@SuppressWarnings("DuplicatedCode") +public class DingtalkScanCodeAuthenticationFilter extends + AbstractIdpAuthenticationProcessingFilter { + public final static String DEFAULT_FILTER_PROCESSES_URI = DINGTALK_SCAN_CODE + .getLoginPathPrefix() + "/*"; + /** + * AntPathRequestMatcher + */ + public static final AntPathRequestMatcher REQUEST_MATCHER = new AntPathRequestMatcher( + DINGTALK_SCAN_CODE.getLoginPathPrefix() + "/" + "{" + PROVIDER_ID + "}", + HttpMethod.GET.name()); + + /** + * Creates a new instance + * + * @param identityProviderRepository the {@link IdentityProviderRepository} + * @param authenticationUserDetails {@link UserIdpService} + */ + public DingtalkScanCodeAuthenticationFilter(IdentityProviderRepository identityProviderRepository, + UserIdpService authenticationUserDetails) { + super(DEFAULT_FILTER_PROCESSES_URI, authenticationUserDetails, identityProviderRepository); + } + + /** + * 钉钉认证 + * + * @param request {@link HttpServletRequest} + * @param response {@link HttpServletRequest} + * @return {@link HttpServletRequest} + * @throws AuthenticationException {@link AuthenticationException} AuthenticationException + */ + @Override + public Authentication attemptAuthentication(HttpServletRequest request, + HttpServletResponse response) throws AuthenticationException, + IOException { + OAuth2AuthorizationRequest authorizationRequest = getOAuth2AuthorizationRequest(request, + response); + TraceUtils.put(UUID.randomUUID().toString()); + RequestMatcher.MatchResult matcher = REQUEST_MATCHER.matcher(request); + Map variables = matcher.getVariables(); + String providerId = variables.get(PROVIDER_ID); + //code + String code = request.getParameter(OAuth2ParameterNames.CODE); + if (StringUtils.isEmpty(code)) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_CODE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + // state + String state = request.getParameter(OAuth2ParameterNames.STATE); + if (StringUtils.isEmpty(state)) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + if (!authorizationRequest.getState().equals(state)) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + //获取身份提供商 + IdentityProviderEntity provider = getIdentityProviderEntity(providerId); + DingTalkIdpScanCodeConfig config = JSONObject.parseObject(provider.getConfig(), + DingTalkIdpScanCodeConfig.class); + if (Objects.isNull(config)) { + logger.error("未查询到钉钉扫码登录配置"); + //无效身份提供商 + OAuth2Error oauth2Error = new OAuth2Error(INVALID_IDP_CONFIG); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + String accessToken = getToken(config); + // 1、通过临时授权码获取授权用户的个人信息 + DefaultDingTalkClient client = new DefaultDingTalkClient(GET_USERINFO_BY_CODE); + OapiSnsGetuserinfoBycodeRequest reqByCodeRequest = new OapiSnsGetuserinfoBycodeRequest(); + // 通过扫描二维码,跳转指定的redirect_uri后,向url中追加的code临时授权码 + reqByCodeRequest.setTmpAuthCode(code); + // 修改appid和appSecret为步骤三创建扫码登录时创建的appid和appSecret + OapiSnsGetuserinfoBycodeResponse userinfoByCodeResp; + try { + userinfoByCodeResp = client.execute(reqByCodeRequest, config.getAppKey(), + config.getAppSecret()); + } catch (ApiException e) { + throw new RuntimeException(e); + } + if (!userinfoByCodeResp.isSuccess()) { + logger.error("钉钉扫码认证获取用户信息失败: [" + userinfoByCodeResp.getBody() + "]"); + throw new TopIamException(userinfoByCodeResp.getErrmsg()); + } + // 2、根据unionid获取userid + client = new DefaultDingTalkClient(GET_USERID_BY_UNIONID); + OapiUserGetUseridByUnionidRequest req = new OapiUserGetUseridByUnionidRequest(); + req.setUnionid(userinfoByCodeResp.getUserInfo().getUnionid()); + OapiUserGetUseridByUnionidResponse rsp; + try { + rsp = client.execute(req, accessToken); + } catch (ApiException e) { + logger.error("钉钉扫码认证获取用户ID失败: [" + e.getErrMsg() + "]"); + throw new TopIamException("钉钉扫码认证获取用户ID失败", e); + } + if (!rsp.isSuccess()) { + logger.error("钉钉扫码认证获取用户ID失败: [" + rsp.getBody() + "]"); + throw new TopIamException(rsp.getErrmsg()); + } + // 3、根据userId获取用户信息 + DingTalkClient v2UserGetResponse = new DefaultDingTalkClient(GET_USERINFO_BY_USERID); + OapiV2UserGetRequest reqGetRequest = new OapiV2UserGetRequest(); + reqGetRequest.setUserid(rsp.getUserid()); + OapiV2UserGetResponse rspGetResponse; + try { + rspGetResponse = v2UserGetResponse.execute(reqGetRequest, accessToken); + } catch (ApiException e) { + logger.error("钉钉扫码认证获取用户信息失败: [" + e.getErrMsg() + "]"); + throw new TopIamException("钉钉扫码认证获取用户信息失败", e); + } + if (!rspGetResponse.isSuccess()) { + logger.error("钉钉扫码认证获取用户信息失败: [" + rspGetResponse.getBody() + "]"); + throw new TopIamException(rspGetResponse.getErrmsg()); + } + //4、执行逻辑 + OapiV2UserGetResponse.UserGetResponse result = rspGetResponse.getResult(); + IdpUser idpUser = IdpUser.builder().openId(result.getUserid()).build(); + return attemptAuthentication(request, response, IdentityProviderType.DINGTALK_SCAN_CODE, + providerId, idpUser); + } + + /** + * 获取token + * + * @return {@link String} + */ + public String getToken(DingTalkIdpScanCodeConfig config) { + if (!Objects.isNull(cache)) { + cache.getIfPresent(OAuth2ParameterNames.ACCESS_TOKEN); + } + Config clientConfig = new Config(); + clientConfig.protocol = "https"; + clientConfig.regionId = "central"; + try { + Client client = new Client(clientConfig); + GetAccessTokenRequest accessTokenRequest = new GetAccessTokenRequest(); + accessTokenRequest.setAppKey(config.getAppKey()); + accessTokenRequest.setAppSecret(config.getAppSecret()); + GetAccessTokenResponseBody body = client.getAccessToken(accessTokenRequest).getBody(); + //放入缓存 + cache = CacheBuilder.newBuilder() + .concurrencyLevel(Runtime.getRuntime().availableProcessors()) + .expireAfterWrite(body.getExpireIn(), TimeUnit.SECONDS).build(); + cache.put(OAuth2ParameterNames.ACCESS_TOKEN, body.getAccessToken()); + return cache.getIfPresent(OAuth2ParameterNames.ACCESS_TOKEN); + } catch (Exception exception) { + throw new RuntimeException(exception.getMessage()); + } + } + + /** + * 缓存 + */ + private Cache cache; + + public static String getLoginUrl(String providerId) { + String url = ServerContextHelp.getPortalPublicBaseUrl() + + DINGTALK_SCAN_CODE.getLoginPathPrefix() + "/" + providerId; + return HttpUrlUtils.format(url); + } + + public static RequestMatcher getRequestMatcher() { + return REQUEST_MATCHER; + } +} diff --git a/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/filter/DingtalkScanCodeAuthorizationRequestGetFilter.java b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/filter/DingtalkScanCodeAuthorizationRequestGetFilter.java new file mode 100644 index 00000000..38c1dfe3 --- /dev/null +++ b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/filter/DingtalkScanCodeAuthorizationRequestGetFilter.java @@ -0,0 +1,172 @@ +/* + * eiam-authentication-dingtalk - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.dingtalk.filter; + +import java.io.IOException; +import java.util.*; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.lang.NonNull; +import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; +import org.springframework.security.crypto.keygen.StringKeyGenerator; +import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; +import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; +import org.springframework.web.filter.OncePerRequestFilter; + +import com.alibaba.fastjson2.JSONObject; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import cn.topiam.employee.authentication.dingtalk.DingTalkIdpScanCodeConfig; +import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; +import cn.topiam.employee.support.result.ApiRestResult; +import cn.topiam.employee.support.util.HttpResponseUtils; +import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.CODE; +import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.RESPONSE_TYPE; + +import static cn.topiam.employee.authentication.dingtalk.constant.DingTalkAuthenticationConstants.APP_ID; +import static cn.topiam.employee.authentication.dingtalk.constant.DingTalkAuthenticationConstants.SCAN_CODE_URL_AUTHORIZE; +import static cn.topiam.employee.authentication.dingtalk.filter.DingtalkScanCodeAuthenticationFilter.getLoginUrl; +import static cn.topiam.employee.common.enums.IdentityProviderType.DINGTALK_SCAN_CODE; + +/** + * 微信扫码登录请求重定向过滤器 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/20 20:22 + */ +@SuppressWarnings("ALL") +public class DingtalkScanCodeAuthorizationRequestGetFilter extends OncePerRequestFilter { + + private final Logger logger = LoggerFactory + .getLogger(DingtalkScanCodeAuthorizationRequestGetFilter.class); + + /** + * 提供商ID + */ + public static final String PROVIDER_ID = "providerId"; + + /** + * AntPathRequestMatcher + */ + public static final AntPathRequestMatcher DINGTALK_SCAN_CODE_REQUEST_MATCHER = new AntPathRequestMatcher( + DINGTALK_SCAN_CODE.getAuthorizationPathPrefix() + "/" + "{" + PROVIDER_ID + "}", + HttpMethod.GET.name()); + + /** + * 认证请求存储库 + */ + private final AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository(); + + private static final StringKeyGenerator DEFAULT_STATE_GENERATOR = new Base64StringKeyGenerator( + Base64.getUrlEncoder()); + private final IdentityProviderRepository identityProviderRepository; + + public DingtalkScanCodeAuthorizationRequestGetFilter(IdentityProviderRepository identityProviderRepository) { + this.identityProviderRepository = identityProviderRepository; + } + + @Override + protected void doFilterInternal(@NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain) throws IOException, + ServletException { + RequestMatcher.MatchResult matcher = DINGTALK_SCAN_CODE_REQUEST_MATCHER.matcher(request); + if (!matcher.isMatch()) { + filterChain.doFilter(request, response); + return; + } + Map variables = matcher.getVariables(); + //校验身份提供商 + String providerId = variables.get(PROVIDER_ID); + Optional optional = identityProviderRepository + .findByIdAndEnabledIsTrue(Long.valueOf(providerId)); + if (optional.isEmpty()) { + logger.error("身份提供商不存在"); + throw new NullPointerException("身份提供商不存在"); + } + IdentityProviderEntity entity = optional.get(); + DingTalkIdpScanCodeConfig config = JSONObject.parseObject(entity.getConfig(), + DingTalkIdpScanCodeConfig.class); + Assert.notNull(config, "钉钉登录配置不能为空"); + //构建授权请求 + //@formatter:off + HashMap<@Nullable String, @Nullable Object> attributes = Maps.newHashMap(); + String redirect = request.getParameter("redirect"); + if (StringUtils.isNoneBlank()) { + attributes.put("redirect", redirect); + } + OAuth2AuthorizationRequest.Builder builder = OAuth2AuthorizationRequest.authorizationCode() + .clientId(config.getAppKey()) + .scopes(Sets.newHashSet("snsapi_login")) + .authorizationUri(SCAN_CODE_URL_AUTHORIZE) + .redirectUri(getLoginUrl(providerId)) + .state(DEFAULT_STATE_GENERATOR.generateKey()) + .attributes(attributes); + builder.parameters(parameters -> { + HashMap linkedParameters = new LinkedHashMap<>(); + parameters.forEach((key, value) -> { + if (OAuth2ParameterNames.CLIENT_ID.equals(key)) { + linkedParameters.put(APP_ID, value); + } + if (OAuth2ParameterNames.SCOPE.equals(key)) { + linkedParameters.put(OAuth2ParameterNames.SCOPE, value); + } + if (OAuth2ParameterNames.STATE.equals(key)) { + linkedParameters.put(OAuth2ParameterNames.STATE, value); + } + if (OAuth2ParameterNames.REDIRECT_URI.equals(key)) { + linkedParameters.put(OAuth2ParameterNames.REDIRECT_URI, value); + } + }); + linkedParameters.put(RESPONSE_TYPE, CODE); + parameters.clear(); + parameters.putAll(linkedParameters); + }); + //@formatter:on + this.writeForAuthorization(request, response, builder.build()); + } + + private void writeForAuthorization(HttpServletRequest request, HttpServletResponse response, + OAuth2AuthorizationRequest authorizationRequest) throws IOException { + this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, + response); + HttpResponseUtils.flushResponseJson(response, HttpStatus.OK.value(), + ApiRestResult.ok(authorizationRequest.getAuthorizationRequestUri())); + } + + public static RequestMatcher getRequestMatcher() { + return DINGTALK_SCAN_CODE_REQUEST_MATCHER; + } +} diff --git a/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/package-info.java b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/package-info.java new file mode 100644 index 00000000..56942859 --- /dev/null +++ b/eiam-authentication/eiam-authentication-dingtalk/src/main/java/cn/topiam/employee/authentication/dingtalk/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-authentication-dingtalk - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.dingtalk; \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-feishu/pom.xml b/eiam-authentication/eiam-authentication-feishu/pom.xml new file mode 100644 index 00000000..4c9b3b6e --- /dev/null +++ b/eiam-authentication/eiam-authentication-feishu/pom.xml @@ -0,0 +1,42 @@ + + + + + eiam-authentication + cn.topiam + 1.0.0-beta1 + ../pom.xml + + 4.0.0 + + eiam-authentication-feishu + jar + + + + cn.topiam + eiam-authentication-core + ${project.version} + + + \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-feishu/src/main/java/cn/topiam/employee/authentication/feishu/configurer/package-info.java b/eiam-authentication/eiam-authentication-feishu/src/main/java/cn/topiam/employee/authentication/feishu/configurer/package-info.java new file mode 100644 index 00000000..553c02d0 --- /dev/null +++ b/eiam-authentication/eiam-authentication-feishu/src/main/java/cn/topiam/employee/authentication/feishu/configurer/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-authentication-feishu - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.feishu.configurer; \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-feishu/src/main/java/cn/topiam/employee/authentication/feishu/filter/FeiShuAuthenticationFilter.java b/eiam-authentication/eiam-authentication-feishu/src/main/java/cn/topiam/employee/authentication/feishu/filter/FeiShuAuthenticationFilter.java new file mode 100644 index 00000000..7c174b7b --- /dev/null +++ b/eiam-authentication/eiam-authentication-feishu/src/main/java/cn/topiam/employee/authentication/feishu/filter/FeiShuAuthenticationFilter.java @@ -0,0 +1,70 @@ +/* + * eiam-authentication-feishu - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.feishu.filter; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.util.matcher.RequestMatcher; + +/** + * 飞书认证过滤器 + * https://open.feishu.cn/document/common-capabilities/sso/web-application-sso/qr-sdk-documentation + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/8 21:11 + */ +public class FeiShuAuthenticationFilter extends AbstractAuthenticationProcessingFilter { + + /** + * Creates a new instance + * + * @param requiresAuthenticationRequestMatcher the {@link RequestMatcher} used to + * determine if authentication is required. Cannot be null. + */ + protected FeiShuAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher) { + super(requiresAuthenticationRequestMatcher); + } + + /** + * qq认证 + * + * @param request {@link HttpServletRequest} + * @param response {@link HttpServletRequest} + * @return {@link HttpServletRequest} + * @throws AuthenticationException AuthenticationException + * @throws IOException IOException + * @throws ServletException ServletException + */ + @Override + public Authentication attemptAuthentication(HttpServletRequest request, + HttpServletResponse response) throws AuthenticationException, + IOException, + ServletException { + //@formatter:off + + //@formatter:on + return null; + } +} diff --git a/eiam-authentication/eiam-authentication-feishu/src/main/java/cn/topiam/employee/authentication/feishu/package-info.java b/eiam-authentication/eiam-authentication-feishu/src/main/java/cn/topiam/employee/authentication/feishu/package-info.java new file mode 100644 index 00000000..9469c16d --- /dev/null +++ b/eiam-authentication/eiam-authentication-feishu/src/main/java/cn/topiam/employee/authentication/feishu/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-authentication-feishu - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.feishu; \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-mfa/pom.xml b/eiam-authentication/eiam-authentication-mfa/pom.xml new file mode 100644 index 00000000..fcfc3053 --- /dev/null +++ b/eiam-authentication/eiam-authentication-mfa/pom.xml @@ -0,0 +1,42 @@ + + + + + eiam-authentication + cn.topiam + 1.0.0-beta1 + ../pom.xml + + 4.0.0 + + eiam-authentication-mfa + jar + + + + cn.topiam + eiam-authentication-core + ${project.version} + + + \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-mfa/src/main/java/cn/topiam/employee/authentication/mfa/package-info.java b/eiam-authentication/eiam-authentication-mfa/src/main/java/cn/topiam/employee/authentication/mfa/package-info.java new file mode 100644 index 00000000..3b90cde1 --- /dev/null +++ b/eiam-authentication/eiam-authentication-mfa/src/main/java/cn/topiam/employee/authentication/mfa/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-authentication-mfa - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.mfa; \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-qq/pom.xml b/eiam-authentication/eiam-authentication-qq/pom.xml new file mode 100644 index 00000000..7c60ea83 --- /dev/null +++ b/eiam-authentication/eiam-authentication-qq/pom.xml @@ -0,0 +1,42 @@ + + + + + eiam-authentication + cn.topiam + 1.0.0-beta1 + ../pom.xml + + 4.0.0 + + eiam-authentication-qq + jar + + + + cn.topiam + eiam-authentication-core + ${project.version} + + + \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/QqIdpOauthConfig.java b/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/QqIdpOauthConfig.java new file mode 100644 index 00000000..b701ec37 --- /dev/null +++ b/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/QqIdpOauthConfig.java @@ -0,0 +1,52 @@ +/* + * eiam-authentication-qq - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.qq; + +import java.io.Serial; + +import javax.validation.constraints.NotBlank; + +import cn.topiam.employee.authentication.common.config.IdentityProviderConfig; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * QQ 认证配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/4/4 23:58 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class QqIdpOauthConfig extends IdentityProviderConfig { + @Serial + private static final long serialVersionUID = -6850223527422243076L; + + /** + * APP ID + */ + @NotBlank(message = "APP ID 不能为空") + private String appId; + + /** + * APP Key + */ + @NotBlank(message = "APP KEY 不能为空") + private String appKey; +} diff --git a/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/configurer/QqOauthAuthenticationConfigurer.java b/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/configurer/QqOauthAuthenticationConfigurer.java new file mode 100644 index 00000000..a5879b62 --- /dev/null +++ b/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/configurer/QqOauthAuthenticationConfigurer.java @@ -0,0 +1,93 @@ +/* + * eiam-authentication-qq - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.qq.configurer; + +import org.springframework.security.config.annotation.web.HttpSecurityBuilder; +import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter; +import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.OrRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; + +import cn.topiam.employee.authentication.common.service.UserIdpService; +import cn.topiam.employee.authentication.qq.filter.QqOAuth2AuthorizationRequestRedirectFilter; +import cn.topiam.employee.authentication.qq.filter.QqOAuth2LoginAuthenticationFilter; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; + +/** + * 认证配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/10 22:58 + */ +public final class QqOauthAuthenticationConfigurer> extends + AbstractAuthenticationFilterConfigurer, QqOAuth2LoginAuthenticationFilter> { + + private final IdentityProviderRepository identityProviderRepository; + private final UserIdpService userIdpService; + + public QqOauthAuthenticationConfigurer(IdentityProviderRepository identityProviderRepository, + UserIdpService userIdpService) { + Assert.notNull(identityProviderRepository, "identityProviderRepository must not be null"); + Assert.notNull(userIdpService, "userIdpService must not be null"); + this.identityProviderRepository = identityProviderRepository; + this.userIdpService = userIdpService; + } + + /** + * Create the {@link RequestMatcher} given a loginProcessingUrl + * + * @param loginProcessingUrl creates the {@link RequestMatcher} based upon the + * loginProcessingUrl + * @return the {@link RequestMatcher} to use based upon the loginProcessingUrl + */ + @Override + protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) { + return new AntPathRequestMatcher(loginProcessingUrl); + } + + @Override + public void init(H http) throws Exception { + //设置登录成功失败处理器 + //QQ扫码登录认证 + QqOAuth2LoginAuthenticationFilter loginAuthenticationFilter = new QqOAuth2LoginAuthenticationFilter( + identityProviderRepository, userIdpService); + this.setAuthenticationFilter(loginAuthenticationFilter); + //处理URL + super.loginProcessingUrl(QqOAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI); + super.init(http); + } + + @Override + public void configure(H http) throws Exception { + //QQ扫码请求重定向 + QqOAuth2AuthorizationRequestRedirectFilter requestRedirectFilter = new QqOAuth2AuthorizationRequestRedirectFilter( + identityProviderRepository); + http.addFilterBefore(requestRedirectFilter, OAuth2AuthorizationRequestRedirectFilter.class); + http.addFilterBefore(this.getAuthenticationFilter(), OAuth2LoginAuthenticationFilter.class); + super.configure(http); + } + + public RequestMatcher getRequestMatcher() { + // + return new OrRequestMatcher(QqOAuth2AuthorizationRequestRedirectFilter.getRequestMatcher(), + QqOAuth2LoginAuthenticationFilter.getRequestMatcher()); + } +} diff --git a/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/constant/QqAuthenticationConstants.java b/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/constant/QqAuthenticationConstants.java new file mode 100644 index 00000000..f1b74eda --- /dev/null +++ b/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/constant/QqAuthenticationConstants.java @@ -0,0 +1,44 @@ +/* + * eiam-authentication-qq - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.portal.idp.qq.constant; + +/** + * 企业微信 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/9 22:19 + */ +public final class QqAuthenticationConstants { + /** + * 获取授权码地址 + */ + public static final String URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize"; + /** + * 获取令牌地址 + */ + public static final String URL_GET_ACCESS_TOKEN = "https://graph.qq.com/oauth2.0/token"; + /** + * 获取 openId 的地址 + */ + public static final String URL_GET_OPEN_ID = "https://graph.qq.com/oauth2.0/me"; + /** + * 获取用户信息的地址 + */ + public static final String URL_GET_USER_INFO = "https://graph.qq.com/user/get_user_info"; + +} diff --git a/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/filter/QqOAuth2AuthorizationRequestRedirectFilter.java b/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/filter/QqOAuth2AuthorizationRequestRedirectFilter.java new file mode 100644 index 00000000..6c1e509e --- /dev/null +++ b/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/filter/QqOAuth2AuthorizationRequestRedirectFilter.java @@ -0,0 +1,139 @@ +/* + * eiam-authentication-qq - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.qq.filter; + +import java.io.IOException; +import java.util.Base64; +import java.util.Map; +import java.util.Optional; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpMethod; +import org.springframework.lang.NonNull; +import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; +import org.springframework.security.crypto.keygen.StringKeyGenerator; +import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; +import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.web.DefaultRedirectStrategy; +import org.springframework.security.web.RedirectStrategy; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; +import org.springframework.web.filter.OncePerRequestFilter; + +import com.alibaba.fastjson2.JSONObject; + +import cn.topiam.employee.authentication.qq.QqIdpOauthConfig; +import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; +import static cn.topiam.employee.authentication.qq.filter.QqOAuth2LoginAuthenticationFilter.getLoginUrl; +import static cn.topiam.employee.common.enums.IdentityProviderType.QQ; +import static cn.topiam.employee.portal.idp.qq.constant.QqAuthenticationConstants.URL_AUTHORIZE; + +/** + * 微信扫码登录请求重定向过滤器 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/20 20:22 + */ +@SuppressWarnings("ALL") +public class QqOAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilter { + + private final Logger logger = LoggerFactory + .getLogger(QqOAuth2AuthorizationRequestRedirectFilter.class); + /** + * 提供商ID + */ + public static final String PROVIDER_ID = "providerId"; + + /** + * AntPathRequestMatcher + */ + public static final AntPathRequestMatcher QQ_REQUEST_MATCHER = new AntPathRequestMatcher( + QQ.getAuthorizationPathPrefix() + "/" + "{" + PROVIDER_ID + "}", HttpMethod.GET.name()); + + /** + * 重定向策略 + */ + private final RedirectStrategy authorizationRedirectStrategy = new DefaultRedirectStrategy(); + + /** + * 认证请求存储库 + */ + private final AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository(); + + private static final StringKeyGenerator DEFAULT_STATE_GENERATOR = new Base64StringKeyGenerator( + Base64.getUrlEncoder()); + private final IdentityProviderRepository identityProviderRepository; + + public QqOAuth2AuthorizationRequestRedirectFilter(IdentityProviderRepository identityProviderRepository) { + this.identityProviderRepository = identityProviderRepository; + } + + @Override + protected void doFilterInternal(@NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain) throws IOException, + ServletException { + RequestMatcher.MatchResult matcher = QQ_REQUEST_MATCHER.matcher(request); + if (!matcher.isMatch()) { + filterChain.doFilter(request, response); + return; + } + Map variables = matcher.getVariables(); + String providerId = variables.get(PROVIDER_ID); + Optional optional = identityProviderRepository + .findByIdAndEnabledIsTrue(Long.valueOf(providerId)); + if (optional.isEmpty()) { + throw new NullPointerException("未查询到身份提供商信息"); + } + IdentityProviderEntity entity = optional.get(); + QqIdpOauthConfig config = JSONObject.parseObject(entity.getConfig(), + QqIdpOauthConfig.class); + Assert.notNull(config, "QQ登录配置不能为空"); + //构建授权请求 + OAuth2AuthorizationRequest.Builder builder = OAuth2AuthorizationRequest.authorizationCode() + .clientId(config.getAppId()).authorizationUri(URL_AUTHORIZE) + .redirectUri(getLoginUrl(providerId)).state(DEFAULT_STATE_GENERATOR.generateKey()); + builder.parameters(parameters -> { + parameters.put(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2ParameterNames.CODE); + }); + this.sendRedirectForAuthorization(request, response, builder.build()); + } + + private void sendRedirectForAuthorization(HttpServletRequest request, + HttpServletResponse response, + OAuth2AuthorizationRequest authorizationRequest) throws IOException { + this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, + response); + this.authorizationRedirectStrategy.sendRedirect(request, response, + authorizationRequest.getAuthorizationRequestUri()); + } + + public static RequestMatcher getRequestMatcher() { + return QQ_REQUEST_MATCHER; + } +} diff --git a/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/filter/QqOAuth2LoginAuthenticationFilter.java b/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/filter/QqOAuth2LoginAuthenticationFilter.java new file mode 100644 index 00000000..5d24ad46 --- /dev/null +++ b/eiam-authentication/eiam-authentication-qq/src/main/java/cn/topiam/employee/authentication/qq/filter/QqOAuth2LoginAuthenticationFilter.java @@ -0,0 +1,170 @@ +/* + * eiam-authentication-qq - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.qq.filter; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpMethod; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.oauth2.core.oidc.OidcScopes; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; + +import cn.topiam.employee.authentication.common.filter.AbstractIdpAuthenticationProcessingFilter; +import cn.topiam.employee.authentication.common.modal.IdpUser; +import cn.topiam.employee.authentication.common.service.UserIdpService; +import cn.topiam.employee.authentication.qq.QqIdpOauthConfig; +import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; +import cn.topiam.employee.core.context.ServerContextHelp; +import cn.topiam.employee.support.exception.TopIamException; +import cn.topiam.employee.support.trace.TraceUtils; +import cn.topiam.employee.support.util.HttpClientUtils; +import static com.nimbusds.oauth2.sdk.GrantType.AUTHORIZATION_CODE; + +import static cn.topiam.employee.authentication.qq.filter.QqOAuth2AuthorizationRequestRedirectFilter.PROVIDER_ID; +import static cn.topiam.employee.common.enums.IdentityProviderType.QQ; +import static cn.topiam.employee.portal.idp.qq.constant.QqAuthenticationConstants.URL_GET_ACCESS_TOKEN; +import static cn.topiam.employee.portal.idp.qq.constant.QqAuthenticationConstants.URL_GET_OPEN_ID; + +/** + * QQ登录 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/8 21:11 + */ +@SuppressWarnings({ "AlibabaClassNamingShouldBeCamel", "DuplicatedCode" }) +public class QqOAuth2LoginAuthenticationFilter extends AbstractIdpAuthenticationProcessingFilter { + final String ERROR_CODE = "error"; + public final static String DEFAULT_FILTER_PROCESSES_URI = QQ.getLoginPathPrefix() + + "/*"; + public static final AntPathRequestMatcher REQUEST_MATCHER = new AntPathRequestMatcher( + QQ.getLoginPathPrefix() + "/" + "{" + PROVIDER_ID + "}", HttpMethod.GET.name()); + + /** + * Creates a new instance + * + * @param identityProviderRepository the {@link IdentityProviderRepository} + * @param authenticationUserDetails {@link UserIdpService} + */ + public QqOAuth2LoginAuthenticationFilter(IdentityProviderRepository identityProviderRepository, + UserIdpService authenticationUserDetails) { + super(DEFAULT_FILTER_PROCESSES_URI, authenticationUserDetails, identityProviderRepository); + } + + /** + * QQ认证 + * + * @param request {@link HttpServletRequest} + * @param response {@link HttpServletRequest} + * @return {@link HttpServletRequest} + * @throws AuthenticationException {@link AuthenticationException} AuthenticationException + */ + @Override + public Authentication attemptAuthentication(HttpServletRequest request, + HttpServletResponse response) throws AuthenticationException, + IOException { + OAuth2AuthorizationRequest authorizationRequest = getOAuth2AuthorizationRequest(request, + response); + TraceUtils.put(UUID.randomUUID().toString()); + RequestMatcher.MatchResult matcher = REQUEST_MATCHER.matcher(request); + Map variables = matcher.getVariables(); + String providerId = variables.get(PROVIDER_ID); + //code + String code = request.getParameter(OAuth2ParameterNames.CODE); + if (StringUtils.isEmpty(code)) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_CODE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + // state + String state = request.getParameter(OAuth2ParameterNames.STATE); + if (StringUtils.isEmpty(state)) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + if (!authorizationRequest.getState().equals(state)) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + //获取身份提供商 + IdentityProviderEntity provider = getIdentityProviderEntity(providerId); + QqIdpOauthConfig config = JSONObject.parseObject(provider.getConfig(), + QqIdpOauthConfig.class); + if (Objects.isNull(config)) { + logger.error("未查询到QQ登录配置"); + //无效身份提供商 + OAuth2Error oauth2Error = new OAuth2Error(INVALID_IDP_CONFIG); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + //获取access token + HashMap param = new HashMap<>(16); + param.put(OAuth2ParameterNames.GRANT_TYPE, AUTHORIZATION_CODE.getValue()); + param.put(OAuth2ParameterNames.CLIENT_ID, config.getAppId().trim()); + param.put(OAuth2ParameterNames.CLIENT_SECRET, config.getAppKey().trim()); + param.put(OAuth2ParameterNames.CODE, code.trim()); + param.put(OAuth2ParameterNames.REDIRECT_URI, getLoginUrl(providerId)); + param.put("fmt", "json"); + //注意:QQ不能使用编码后的get请求,否则会报 {"error_description":"redirect uri is illegal","error":100010} + JSONObject result = JSON.parseObject(HttpClientUtils.doGet(URL_GET_ACCESS_TOKEN, param)); + if (!Objects.isNull(result.getString(ERROR_CODE))) { + logger.error("获取access_token发生错误: {}" + result.toJSONString()); + throw new TopIamException("获取access_token发生错误: " + result.toJSONString()); + } + // 获取openId信息 + param = new HashMap<>(16); + param.put(OAuth2ParameterNames.ACCESS_TOKEN, + result.getString(OAuth2ParameterNames.ACCESS_TOKEN)); + param.put("fmt", "json"); + result = JSON.parseObject(HttpClientUtils.doGet(URL_GET_OPEN_ID, param)); + if (!Objects.isNull(result.getString(ERROR_CODE))) { + logger.error("获取QQ用户OpenID发生错误: {}" + result.toJSONString()); + throw new TopIamException("获取QQ用户OpenID发生错误: " + result.toJSONString()); + } + // 返回 + String openId = result.getString(OidcScopes.OPENID); + IdpUser idpUser = IdpUser.builder().openId(openId).build(); + return attemptAuthentication(request, response, QQ, providerId, idpUser); + + } + + public static String getLoginUrl(String providerId) { + String url = ServerContextHelp.getPortalPublicBaseUrl() + "/" + QQ.getLoginPathPrefix() + + "/" + providerId; + return url.replaceAll("(?. + */ +package cn.topiam.employee.authentication.qq; \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-sms/pom.xml b/eiam-authentication/eiam-authentication-sms/pom.xml new file mode 100644 index 00000000..de986aa8 --- /dev/null +++ b/eiam-authentication/eiam-authentication-sms/pom.xml @@ -0,0 +1,42 @@ + + + + + eiam-authentication + cn.topiam + 1.0.0-beta1 + ../pom.xml + + 4.0.0 + + eiam-authentication-sms + jar + + + + cn.topiam + eiam-authentication-core + ${project.version} + + + \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/configurer/SmsAuthenticationConfigurer.java b/eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/configurer/SmsAuthenticationConfigurer.java new file mode 100644 index 00000000..8756a373 --- /dev/null +++ b/eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/configurer/SmsAuthenticationConfigurer.java @@ -0,0 +1,78 @@ +/* + * eiam-authentication-sms - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.sms.configurer; + +import org.springframework.security.config.annotation.web.HttpSecurityBuilder; +import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; + +import cn.topiam.employee.authentication.sms.filter.SmsAuthenticationFilter; + +/** + * 认证配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/10 22:58 + */ +public final class SmsAuthenticationConfigurer> extends + AbstractAuthenticationFilterConfigurer, SmsAuthenticationFilter> { + private final UserDetailsService userDetailsService; + + public SmsAuthenticationConfigurer(UserDetailsService userDetailsService) { + Assert.notNull(userDetailsService, "userDetailsService must not be null"); + this.userDetailsService = userDetailsService; + } + + /** + * Create the {@link RequestMatcher} given a loginProcessingUrl + * + * @param loginProcessingUrl creates the {@link RequestMatcher} based upon the + * loginProcessingUrl + * @return the {@link RequestMatcher} to use based upon the loginProcessingUrl + */ + @Override + protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) { + return new AntPathRequestMatcher(loginProcessingUrl); + } + + @Override + public void init(H http) throws Exception { + //SMS + SmsAuthenticationFilter loginAuthenticationFilter = new SmsAuthenticationFilter( + userDetailsService); + this.setAuthenticationFilter(loginAuthenticationFilter); + //处理URL + super.loginProcessingUrl(SmsAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI); + super.init(http); + } + + @Override + public void configure(H http) throws Exception { + http.addFilterAfter(this.getAuthenticationFilter(), + UsernamePasswordAuthenticationFilter.class); + super.configure(http); + } + + public RequestMatcher getRequestMatcher() { + return SmsAuthenticationFilter.getRequestMatcher(); + } +} diff --git a/eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/configurer/package-info.java b/eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/configurer/package-info.java new file mode 100644 index 00000000..355381c4 --- /dev/null +++ b/eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/configurer/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-authentication-sms - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.sms.configurer; \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/filter/SmsAuthenticationFilter.java b/eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/filter/SmsAuthenticationFilter.java new file mode 100644 index 00000000..e11fb81b --- /dev/null +++ b/eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/filter/SmsAuthenticationFilter.java @@ -0,0 +1,148 @@ +/* + * eiam-authentication-sms - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.sms.filter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; + +import cn.topiam.employee.core.security.authentication.SmsAuthentication; +import cn.topiam.employee.support.result.ApiRestResult; +import cn.topiam.employee.support.util.HttpResponseUtils; +import static cn.topiam.employee.common.constants.AuthorizeConstants.SMS_LOGIN; +import static cn.topiam.employee.support.exception.enums.ExceptionStatus.EX000102; + +/** + * SmsAuthenticationFilter + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/16 21:34 + */ +public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter { + + public static final String PHONE_KEY = "phone"; + /** + * 请求方法 + */ + public static final String METHOD = "POST"; + + private String phoneParameter = PHONE_KEY; + /** + * 是否值处理POST请求 + */ + private boolean postOnly = true; + + public final static String DEFAULT_FILTER_PROCESSES_URI = SMS_LOGIN; + + public static final RequestMatcher SMS_LOGIN_MATCHER = new AntPathRequestMatcher( + DEFAULT_FILTER_PROCESSES_URI, HttpMethod.POST.name()); + + public SmsAuthenticationFilter(UserDetailsService userDetailsService) { + super(SMS_LOGIN_MATCHER); + this.userDetailsService = userDetailsService; + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, + HttpServletResponse response) throws AuthenticationException { + try { + if (postOnly && !METHOD.equalsIgnoreCase(request.getMethod())) { + throw new AuthenticationServiceException( + "Authentication method not supported: " + request.getMethod()); + } + // 获取手机号 + String phone = StringUtils.defaultString(obtainUsername(request), "").trim(); + UserDetails userDetails = userDetailsService.loadUserByUsername(phone); + SmsAuthentication authentication = new SmsAuthentication(userDetails, phone, + userDetails.getAuthorities()); + // Allow subclasses to set the "details" property + setDetails(request, authentication); + return authentication; + } catch (Exception e) { + HttpResponseUtils.flushResponseJson(response, HttpStatus.BAD_REQUEST.value(), + ApiRestResult.builder().status(EX000102.getCode()).message(EX000102.getMessage()) + .build()); + return null; + } + } + + /** + * Enables subclasses to override the composition of the username, such as + * by including additional values and a separator. + * + * @param request so that request attributes can be retrieved + * @return the username that will be presented in the + * Authentication request token to the + * AuthenticationManager + */ + protected String obtainUsername(HttpServletRequest request) { + return request.getParameter(phoneParameter); + } + + /** + * Provided so that subclasses may configure what is put into the + * authentication request's details property. + * + * @param request that an authentication request is being created for + * @param authRequest the authentication request object that should have its details + * set + */ + protected void setDetails(HttpServletRequest request, SmsAuthentication authRequest) { + authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); + } + + /** + * Defines whether only HTTP POST requests will be allowed by this filter. + * If set to true, and an authentication request is received which is not a + * POST request, an exception will be raised immediately and authentication + * will not be attempted. The unsuccessfulAuthentication() method + * will be called as if handling a failed authentication. + *

+ * Defaults to true but may be overridden by subclasses. + */ + public void setPostOnly(boolean postOnly) { + this.postOnly = postOnly; + } + + public final String getPhoneParameter() { + return phoneParameter; + } + + public void setPhoneParameter(String phoneParameter) { + Assert.hasText(phoneParameter, "Mobile parameter must not be empty or null"); + this.phoneParameter = phoneParameter; + } + + public static RequestMatcher getRequestMatcher() { + return SMS_LOGIN_MATCHER; + } + + private final UserDetailsService userDetailsService; +} diff --git a/eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/filter/package-info.java b/eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/filter/package-info.java new file mode 100644 index 00000000..2b01939c --- /dev/null +++ b/eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/filter/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-authentication-sms - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.sms.filter; \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/package-info.java b/eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/package-info.java new file mode 100644 index 00000000..2c8abe80 --- /dev/null +++ b/eiam-authentication/eiam-authentication-sms/src/main/java/cn/topiam/employee/authentication/sms/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-authentication-sms - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.sms; \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-wechat/pom.xml b/eiam-authentication/eiam-authentication-wechat/pom.xml new file mode 100644 index 00000000..7d6bf0c9 --- /dev/null +++ b/eiam-authentication/eiam-authentication-wechat/pom.xml @@ -0,0 +1,42 @@ + + + + + eiam-authentication + cn.topiam + 1.0.0-beta1 + ../pom.xml + + 4.0.0 + + eiam-authentication-wechat + jar + + + + cn.topiam + eiam-authentication-core + ${project.version} + + + \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/WeChatIdpScanCodeConfig.java b/eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/WeChatIdpScanCodeConfig.java new file mode 100644 index 00000000..7cd73424 --- /dev/null +++ b/eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/WeChatIdpScanCodeConfig.java @@ -0,0 +1,51 @@ +/* + * eiam-authentication-wechat - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.wechat; + +import java.io.Serial; + +import javax.validation.constraints.NotBlank; + +import cn.topiam.employee.authentication.common.config.IdentityProviderConfig; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 微信开放平台扫码登录 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/9 22:07 21: + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WeChatIdpScanCodeConfig extends IdentityProviderConfig { + @Serial + private static final long serialVersionUID = -5831048603320371078L; + /** + * 客户端id + */ + @NotBlank(message = "应用AppId不能为空") + private String appId; + + /** + * 客户端Secret + */ + @NotBlank(message = "应用AppId不能为空") + private String appSecret; +} diff --git a/eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/configurer/WeChatScanCodeAuthenticationConfigurer.java b/eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/configurer/WeChatScanCodeAuthenticationConfigurer.java new file mode 100644 index 00000000..d825c90b --- /dev/null +++ b/eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/configurer/WeChatScanCodeAuthenticationConfigurer.java @@ -0,0 +1,93 @@ +/* + * eiam-authentication-wechat - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.wechat.configurer; + +import org.springframework.security.config.annotation.web.HttpSecurityBuilder; +import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter; +import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.OrRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; + +import cn.topiam.employee.authentication.common.service.UserIdpService; +import cn.topiam.employee.authentication.wechat.filter.WeChatScanCodeAuthorizationRequestRedirectFilter; +import cn.topiam.employee.authentication.wechat.filter.WeChatScanCodeLoginAuthenticationFilter; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; + +/** + * 认证配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/10 22:58 + */ +public final class WeChatScanCodeAuthenticationConfigurer> extends + AbstractAuthenticationFilterConfigurer, WeChatScanCodeLoginAuthenticationFilter> { + + private final IdentityProviderRepository identityProviderRepository; + private final UserIdpService userIdpService; + + public WeChatScanCodeAuthenticationConfigurer(IdentityProviderRepository identityProviderRepository, + UserIdpService userIdpService) { + Assert.notNull(identityProviderRepository, "identityProviderRepository must not be null"); + Assert.notNull(userIdpService, "userIdpService must not be null"); + this.identityProviderRepository = identityProviderRepository; + this.userIdpService = userIdpService; + } + + /** + * Create the {@link RequestMatcher} given a loginProcessingUrl + * + * @param loginProcessingUrl creates the {@link RequestMatcher} based upon the + * loginProcessingUrl + * @return the {@link RequestMatcher} to use based upon the loginProcessingUrl + */ + @Override + protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) { + return new AntPathRequestMatcher(loginProcessingUrl); + } + + @Override + public void init(H http) throws Exception { + //微信扫码登录认证 + WeChatScanCodeLoginAuthenticationFilter loginAuthenticationFilter = new WeChatScanCodeLoginAuthenticationFilter( + identityProviderRepository, userIdpService); + this.setAuthenticationFilter(loginAuthenticationFilter); + //处理URL + super.loginProcessingUrl( + WeChatScanCodeLoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI); + super.init(http); + } + + @Override + public void configure(H http) throws Exception { + //微信扫码请求重定向 + WeChatScanCodeAuthorizationRequestRedirectFilter requestRedirectFilter = new WeChatScanCodeAuthorizationRequestRedirectFilter( + identityProviderRepository); + http.addFilterBefore(requestRedirectFilter, OAuth2AuthorizationRequestRedirectFilter.class); + http.addFilterBefore(this.getAuthenticationFilter(), OAuth2LoginAuthenticationFilter.class); + super.configure(http); + } + + public RequestMatcher getRequestMatcher() { + return new OrRequestMatcher( + WeChatScanCodeAuthorizationRequestRedirectFilter.getRequestMatcher(), + WeChatScanCodeLoginAuthenticationFilter.getRequestMatcher()); + } +} diff --git a/eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/constant/WeChatAuthenticationConstants.java b/eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/constant/WeChatAuthenticationConstants.java new file mode 100644 index 00000000..7de04231 --- /dev/null +++ b/eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/constant/WeChatAuthenticationConstants.java @@ -0,0 +1,37 @@ +/* + * eiam-authentication-wechat - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.wechat.constant; + +/** + * 微信认证常量 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/9 22:19 + */ +public final class WeChatAuthenticationConstants { + + public static final String AUTHORIZATION_REQUEST = "https://open.weixin.qq.com/connect/qrconnect"; + public static final String ACCESS_TOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token"; + public static final String USER_INFO = "https://api.weixin.qq.com/sns/userinfo"; + public static final String APP_ID = "appId"; + public static final String SNSAPI_LOGIN = "snsapi_login"; + public static final String ERROR_CODE = "errcode"; + public static final String SECRET = "secret"; + public static final String HREF = "href"; + +} diff --git a/eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/filter/WeChatScanCodeAuthorizationRequestRedirectFilter.java b/eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/filter/WeChatScanCodeAuthorizationRequestRedirectFilter.java new file mode 100644 index 00000000..8f981816 --- /dev/null +++ b/eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/filter/WeChatScanCodeAuthorizationRequestRedirectFilter.java @@ -0,0 +1,175 @@ +/* + * eiam-authentication-wechat - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.wechat.filter; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpMethod; +import org.springframework.lang.NonNull; +import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; +import org.springframework.security.crypto.keygen.StringKeyGenerator; +import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; +import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.web.DefaultRedirectStrategy; +import org.springframework.security.web.RedirectStrategy; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; +import org.springframework.web.filter.OncePerRequestFilter; + +import com.alibaba.fastjson2.JSONObject; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import cn.topiam.employee.authentication.wechat.WeChatIdpScanCodeConfig; +import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; +import static cn.topiam.employee.authentication.wechat.constant.WeChatAuthenticationConstants.*; +import static cn.topiam.employee.common.enums.IdentityProviderType.WECHAT_SCAN_CODE; + +/** + * 微信扫码登录请求重定向过滤器 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/20 20:22 + */ +@SuppressWarnings("DuplicatedCode") +public class WeChatScanCodeAuthorizationRequestRedirectFilter extends OncePerRequestFilter { + + private final Logger logger = LoggerFactory + .getLogger(WeChatScanCodeAuthorizationRequestRedirectFilter.class); + + /** + * 提供商ID + */ + public static final String PROVIDER_ID = "providerId"; + + /** + * AntPathRequestMatcher + */ + public static final AntPathRequestMatcher WE_CHAT_SCAN_CODE_REQUEST_MATCHER = new AntPathRequestMatcher( + WECHAT_SCAN_CODE.getAuthorizationPathPrefix() + "/" + "{" + PROVIDER_ID + "}", + HttpMethod.GET.name()); + + /** + * 重定向策略 + */ + private final RedirectStrategy authorizationRedirectStrategy = new DefaultRedirectStrategy(); + + /** + * 认证请求存储库 + */ + private final AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository(); + + private static final StringKeyGenerator DEFAULT_STATE_GENERATOR = new Base64StringKeyGenerator( + Base64.getUrlEncoder()); + private final IdentityProviderRepository identityProviderRepository; + + public WeChatScanCodeAuthorizationRequestRedirectFilter(IdentityProviderRepository identityProviderRepository) { + this.identityProviderRepository = identityProviderRepository; + } + + @Override + protected void doFilterInternal(@NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain) throws IOException, + ServletException { + RequestMatcher.MatchResult matcher = WE_CHAT_SCAN_CODE_REQUEST_MATCHER.matcher(request); + if (!matcher.isMatch()) { + filterChain.doFilter(request, response); + return; + } + Map variables = matcher.getVariables(); + String providerId = variables.get(PROVIDER_ID); + Optional optional = identityProviderRepository + .findByIdAndEnabledIsTrue(Long.valueOf(providerId)); + if (optional.isEmpty()) { + throw new NullPointerException("未查询到身份提供商信息"); + } + IdentityProviderEntity entity = optional.get(); + WeChatIdpScanCodeConfig config = JSONObject.parseObject(entity.getConfig(), + WeChatIdpScanCodeConfig.class); + Assert.notNull(config, "微信扫码登录配置不能为空"); + //构建授权请求 + //@formatter:off + HashMap<@Nullable String, @Nullable Object> attributes = Maps.newHashMap(); + OAuth2AuthorizationRequest.Builder builder = OAuth2AuthorizationRequest.authorizationCode() + .clientId(config.getAppId()) + .scopes(Sets.newHashSet(SNSAPI_LOGIN)) + .authorizationUri(AUTHORIZATION_REQUEST) + .redirectUri(WeChatScanCodeLoginAuthenticationFilter.getLoginUrl(providerId)) + .state(DEFAULT_STATE_GENERATOR.generateKey()) + .attributes(attributes); + //@formatter:on + builder.parameters(parameters -> { + HashMap linkedParameters = new LinkedHashMap<>(); + parameters.forEach((key, value) -> { + if (OAuth2ParameterNames.CLIENT_ID.equals(key)) { + linkedParameters.put(APP_ID, value); + } + if (OAuth2ParameterNames.SCOPE.equals(key)) { + linkedParameters.put(OAuth2ParameterNames.SCOPE, value); + } + if (OAuth2ParameterNames.STATE.equals(key)) { + linkedParameters.put(OAuth2ParameterNames.STATE, value); + } + if (OAuth2ParameterNames.REDIRECT_URI.equals(key)) { + linkedParameters.put(OAuth2ParameterNames.REDIRECT_URI, value); + } + }); + linkedParameters.put(HREF, STYLE_BASE64); + parameters.clear(); + parameters.putAll(linkedParameters); + }); + this.sendRedirectForAuthorization(request, response, builder.build()); + } + + private void sendRedirectForAuthorization(HttpServletRequest request, + HttpServletResponse response, + OAuth2AuthorizationRequest authorizationRequest) throws IOException { + this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, + response); + this.authorizationRedirectStrategy.sendRedirect(request, response, + authorizationRequest.getAuthorizationRequestUri()); + } + + private final static String STYLE = "" + + ".impowerBox .qrcode {width: 280px;border: none;margin-top:10px;}\n" + + ".impowerBox .title {display: none;}\n" + + ".impowerBox .info {display: none;}\n" + + ".status_icon {display: none}\n" + + ".impowerBox .status {text-align: center;} "; + private final static String STYLE_BASE64 = "data:text/css;base64," + Base64.getEncoder() + .encodeToString(STYLE.getBytes(StandardCharsets.UTF_8)); + + public static RequestMatcher getRequestMatcher() { + return WE_CHAT_SCAN_CODE_REQUEST_MATCHER; + } +} diff --git a/eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/filter/WeChatScanCodeLoginAuthenticationFilter.java b/eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/filter/WeChatScanCodeLoginAuthenticationFilter.java new file mode 100644 index 00000000..b5006916 --- /dev/null +++ b/eiam-authentication/eiam-authentication-wechat/src/main/java/cn/topiam/employee/authentication/wechat/filter/WeChatScanCodeLoginAuthenticationFilter.java @@ -0,0 +1,168 @@ +/* + * eiam-authentication-wechat - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.wechat.filter; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpMethod; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.oauth2.core.oidc.OidcScopes; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; + +import cn.topiam.employee.authentication.common.filter.AbstractIdpAuthenticationProcessingFilter; +import cn.topiam.employee.authentication.common.modal.IdpUser; +import cn.topiam.employee.authentication.common.service.UserIdpService; +import cn.topiam.employee.authentication.wechat.WeChatIdpScanCodeConfig; +import cn.topiam.employee.authentication.wechat.constant.WeChatAuthenticationConstants; +import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity; +import cn.topiam.employee.common.enums.IdentityProviderType; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; +import cn.topiam.employee.core.context.ServerContextHelp; +import cn.topiam.employee.support.exception.TopIamException; +import cn.topiam.employee.support.util.HttpClientUtils; +import static org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE; + +import static cn.topiam.employee.authentication.wechat.constant.WeChatAuthenticationConstants.*; +import static cn.topiam.employee.authentication.wechat.filter.WeChatScanCodeAuthorizationRequestRedirectFilter.PROVIDER_ID; +import static cn.topiam.employee.common.enums.IdentityProviderType.WECHAT_SCAN_CODE; + +/** + * 微信扫码登录过滤器 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/8 21:11 + */ +public class WeChatScanCodeLoginAuthenticationFilter extends + AbstractIdpAuthenticationProcessingFilter { + + public final static String DEFAULT_FILTER_PROCESSES_URI = WECHAT_SCAN_CODE + .getLoginPathPrefix() + "/*"; + public static final AntPathRequestMatcher REQUEST_MATCHER = new AntPathRequestMatcher( + WECHAT_SCAN_CODE.getLoginPathPrefix() + "/" + "{" + PROVIDER_ID + "}", + HttpMethod.GET.name()); + + /** + * Creates a new instance + * + * @param identityProviderRepository the {@link IdentityProviderRepository} + * @param authenticationUserDetails {@link UserIdpService} + */ + public WeChatScanCodeLoginAuthenticationFilter(IdentityProviderRepository identityProviderRepository, + UserIdpService authenticationUserDetails) { + super(DEFAULT_FILTER_PROCESSES_URI, authenticationUserDetails, identityProviderRepository); + } + + /** + * 微信认证 + * + * @param request {@link HttpServletRequest} + * @param response {@link HttpServletRequest} + * @return {@link HttpServletRequest} + * @throws AuthenticationException {@link AuthenticationException} AuthenticationException + */ + @Override + public Authentication attemptAuthentication(HttpServletRequest request, + HttpServletResponse response) throws AuthenticationException, + IOException { + OAuth2AuthorizationRequest authorizationRequest = getOAuth2AuthorizationRequest(request, + response); + RequestMatcher.MatchResult matcher = REQUEST_MATCHER.matcher(request); + Map variables = matcher.getVariables(); + String providerId = variables.get(PROVIDER_ID); + //code + String code = request.getParameter(OAuth2ParameterNames.CODE); + if (StringUtils.isEmpty(code)) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_CODE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + // state + String state = request.getParameter(OAuth2ParameterNames.STATE); + if (StringUtils.isEmpty(state)) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + if (!authorizationRequest.getState().equals(state)) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + //获取身份提供商 + IdentityProviderEntity provider = getIdentityProviderEntity(providerId); + WeChatIdpScanCodeConfig config = JSONObject.parseObject(provider.getConfig(), + WeChatIdpScanCodeConfig.class); + if (Objects.isNull(config)) { + logger.error("未查询到微信扫码登录配置"); + //无效身份提供商 + OAuth2Error oauth2Error = new OAuth2Error( + AbstractIdpAuthenticationProcessingFilter.INVALID_IDP_CONFIG); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + //获取access token + HashMap param = new HashMap<>(16); + param.put(APP_ID, config.getAppId()); + param.put(SECRET, config.getAppSecret()); + param.put(OAuth2ParameterNames.CODE, code); + param.put(OAuth2ParameterNames.GRANT_TYPE, AUTHORIZATION_CODE.getValue()); + JSONObject result = JSON + .parseObject(HttpClientUtils.get(WeChatAuthenticationConstants.ACCESS_TOKEN, param)); + if (result.containsKey(ERROR_CODE)) { + logger.error("获取access_token发生错误: " + result.toJSONString()); + throw new TopIamException("获取access_token发生错误: " + result.toJSONString()); + } + // 获取user信息 + param = new HashMap<>(16); + param.put(OAuth2ParameterNames.ACCESS_TOKEN, + result.getString(OAuth2ParameterNames.ACCESS_TOKEN)); + param.put(OidcScopes.OPENID, result.getString(OidcScopes.OPENID)); + result = JSON + .parseObject(HttpClientUtils.get(WeChatAuthenticationConstants.USER_INFO, param)); + if (result.containsKey(ERROR_CODE)) { + logger.error("获取微信用户个人信息发生错误: " + result.toJSONString()); + throw new TopIamException("获取微信用户个人信息发生错误: " + result.toJSONString()); + } + // 返回 + IdpUser idpUser = IdpUser.builder().openId(param.get(OidcScopes.OPENID)).build(); + return attemptAuthentication(request, response, IdentityProviderType.WECHAT_SCAN_CODE, + providerId, idpUser); + } + + public static String getLoginUrl(String providerId) { + String url = ServerContextHelp.getPortalPublicBaseUrl() + + WECHAT_SCAN_CODE.getLoginPathPrefix() + "/" + providerId; + return url.replaceAll("(?. + */ +package cn.topiam.employee.authentication.wechat; \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-wechatwork/pom.xml b/eiam-authentication/eiam-authentication-wechatwork/pom.xml new file mode 100644 index 00000000..b9cda1de --- /dev/null +++ b/eiam-authentication/eiam-authentication-wechatwork/pom.xml @@ -0,0 +1,42 @@ + + + + + eiam-authentication + cn.topiam + 1.0.0-beta1 + ../pom.xml + + 4.0.0 + + eiam-authentication-wechatwork + jar + + + + cn.topiam + eiam-authentication-core + ${project.version} + + + \ No newline at end of file diff --git a/eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/WeChatWorkIdpScanCodeConfig.java b/eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/WeChatWorkIdpScanCodeConfig.java new file mode 100644 index 00000000..f5459f5d --- /dev/null +++ b/eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/WeChatWorkIdpScanCodeConfig.java @@ -0,0 +1,58 @@ +/* + * eiam-authentication-wechatwork - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.wechatwork; + +import java.io.Serial; + +import javax.validation.constraints.NotBlank; + +import cn.topiam.employee.authentication.common.config.IdentityProviderConfig; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 钉钉扫码配置参数 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/8 21:36 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WeChatWorkIdpScanCodeConfig extends IdentityProviderConfig { + @Serial + private static final long serialVersionUID = -6850223527422243076L; + + /** + * 企业ID + */ + @NotBlank(message = "企业ID不能为空") + private String corpId; + + /** + * 应用AgentID + */ + @NotBlank(message = "应用AgentID不能为空") + private String agentId; + + /** + * 应用Secret + */ + @NotBlank(message = "应用Secret不能为空") + private String appSecret; +} diff --git a/eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/configurer/WeChatWorkScanCodeAuthenticationConfigurer.java b/eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/configurer/WeChatWorkScanCodeAuthenticationConfigurer.java new file mode 100644 index 00000000..9cfb16a7 --- /dev/null +++ b/eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/configurer/WeChatWorkScanCodeAuthenticationConfigurer.java @@ -0,0 +1,94 @@ +/* + * eiam-authentication-wechatwork - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.wechatwork.configurer; + +import org.springframework.security.config.annotation.web.HttpSecurityBuilder; +import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter; +import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.OrRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; + +import cn.topiam.employee.authentication.common.service.UserIdpService; +import cn.topiam.employee.authentication.wechatwork.filter.WeChatWorkScanCodeAuthorizationRequestRedirectFilter; +import cn.topiam.employee.authentication.wechatwork.filter.WeChatWorkScanCodeLoginAuthenticationFilter; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; + +/** + * 认证配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/10 22:58 + */ +public final class WeChatWorkScanCodeAuthenticationConfigurer> + extends + AbstractAuthenticationFilterConfigurer, WeChatWorkScanCodeLoginAuthenticationFilter> { + + private final IdentityProviderRepository identityProviderRepository; + private final UserIdpService userIdpService; + + public WeChatWorkScanCodeAuthenticationConfigurer(IdentityProviderRepository identityProviderRepository, + UserIdpService userIdpService) { + Assert.notNull(identityProviderRepository, "identityProviderRepository must not be null"); + Assert.notNull(userIdpService, "userIdpService must not be null"); + this.identityProviderRepository = identityProviderRepository; + this.userIdpService = userIdpService; + } + + /** + * Create the {@link RequestMatcher} given a loginProcessingUrl + * + * @param loginProcessingUrl creates the {@link RequestMatcher} based upon the + * loginProcessingUrl + * @return the {@link RequestMatcher} to use based upon the loginProcessingUrl + */ + @Override + protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) { + return new AntPathRequestMatcher(loginProcessingUrl); + } + + @Override + public void init(H http) throws Exception { + //微信扫码登录认证 + WeChatWorkScanCodeLoginAuthenticationFilter loginAuthenticationFilter = new WeChatWorkScanCodeLoginAuthenticationFilter( + identityProviderRepository, userIdpService); + this.setAuthenticationFilter(loginAuthenticationFilter); + //处理URL + super.loginProcessingUrl( + WeChatWorkScanCodeLoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI); + super.init(http); + } + + @Override + public void configure(H http) throws Exception { + //企业微信扫码请求重定向 + WeChatWorkScanCodeAuthorizationRequestRedirectFilter requestRedirectFilter = new WeChatWorkScanCodeAuthorizationRequestRedirectFilter( + identityProviderRepository); + http.addFilterBefore(requestRedirectFilter, OAuth2AuthorizationRequestRedirectFilter.class); + http.addFilterBefore(this.getAuthenticationFilter(), OAuth2LoginAuthenticationFilter.class); + super.configure(http); + } + + public RequestMatcher getRequestMatcher() { + return new OrRequestMatcher( + WeChatWorkScanCodeAuthorizationRequestRedirectFilter.getRequestMatcher(), + WeChatWorkScanCodeLoginAuthenticationFilter.getRequestMatcher()); + } +} diff --git a/eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/constant/WeChatWorkAuthenticationConstants.java b/eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/constant/WeChatWorkAuthenticationConstants.java new file mode 100644 index 00000000..de4aa9a7 --- /dev/null +++ b/eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/constant/WeChatWorkAuthenticationConstants.java @@ -0,0 +1,37 @@ +/* + * eiam-authentication-wechatwork - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.wechatwork.constant; + +/** + * 企业微信 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/9 22:19 + */ +public final class WeChatWorkAuthenticationConstants { + public final static String APP_ID = "appid"; + public final static String AGENT_ID = "agentid"; + public final static String HREF = "href"; + public final static String LOGIN_TYPE = "login_type"; + public final static String JSSDK = "jssdk"; + public final static String URL_AUTHORIZE = "https://open.work.weixin.qq.com/wwopen/sso/v1/qrConnect"; + + public final static String GET_USER_INFO = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo"; + + public final static String GET_TOKEN = "https://qyapi.weixin.qq.com/cgi-bin/gettoken"; +} diff --git a/eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/filter/WeChatWorkScanCodeAuthorizationRequestRedirectFilter.java b/eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/filter/WeChatWorkScanCodeAuthorizationRequestRedirectFilter.java new file mode 100644 index 00000000..2f2ea065 --- /dev/null +++ b/eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/filter/WeChatWorkScanCodeAuthorizationRequestRedirectFilter.java @@ -0,0 +1,164 @@ +/* + * eiam-authentication-wechatwork - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.wechatwork.filter; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpMethod; +import org.springframework.lang.NonNull; +import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; +import org.springframework.security.crypto.keygen.StringKeyGenerator; +import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; +import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.web.DefaultRedirectStrategy; +import org.springframework.security.web.RedirectStrategy; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; +import org.springframework.web.filter.OncePerRequestFilter; + +import com.alibaba.fastjson2.JSONObject; + +import cn.topiam.employee.authentication.wechatwork.WeChatWorkIdpScanCodeConfig; +import cn.topiam.employee.authentication.wechatwork.constant.WeChatWorkAuthenticationConstants; +import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; +import static cn.topiam.employee.common.enums.IdentityProviderType.WECHATWORK_SCAN_CODE; + +/** + * 微信扫码登录请求重定向过滤器 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/20 20:22 + */ +@SuppressWarnings("DuplicatedCode") +public class WeChatWorkScanCodeAuthorizationRequestRedirectFilter extends OncePerRequestFilter { + + private final Logger logger = LoggerFactory + .getLogger(WeChatWorkScanCodeAuthorizationRequestRedirectFilter.class); + + /** + * 提供商ID + */ + public static final String PROVIDER_ID = "providerId"; + + public static final AntPathRequestMatcher WECHAT_WORK_REQUEST_MATCHER = new AntPathRequestMatcher( + WECHATWORK_SCAN_CODE.getAuthorizationPathPrefix() + "/" + "{" + PROVIDER_ID + "}", + HttpMethod.GET.name()); + + /** + * 重定向策略 + */ + private final RedirectStrategy authorizationRedirectStrategy = new DefaultRedirectStrategy(); + + /** + * 认证请求存储库 + */ + private final AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository(); + + private static final StringKeyGenerator DEFAULT_STATE_GENERATOR = new Base64StringKeyGenerator( + Base64.getUrlEncoder()); + private final IdentityProviderRepository identityProviderRepository; + + public WeChatWorkScanCodeAuthorizationRequestRedirectFilter(IdentityProviderRepository identityProviderRepository) { + this.identityProviderRepository = identityProviderRepository; + } + + @Override + protected void doFilterInternal(@NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain) throws IOException, + ServletException { + RequestMatcher.MatchResult matcher = WECHAT_WORK_REQUEST_MATCHER.matcher(request); + if (!matcher.isMatch()) { + filterChain.doFilter(request, response); + return; + } + Map variables = matcher.getVariables(); + String providerId = variables.get(PROVIDER_ID); + Optional optional = identityProviderRepository + .findByIdAndEnabledIsTrue(Long.valueOf(providerId)); + if (optional.isEmpty()) { + throw new NullPointerException("未查询到身份提供商信息"); + } + IdentityProviderEntity entity = optional.get(); + WeChatWorkIdpScanCodeConfig config = JSONObject.parseObject(entity.getConfig(), + WeChatWorkIdpScanCodeConfig.class); + Assert.notNull(config, "企业微信扫码登录配置不能为空"); + //构建授权请求 + OAuth2AuthorizationRequest.Builder builder = OAuth2AuthorizationRequest.authorizationCode() + .clientId(config.getCorpId()) + .authorizationUri(WeChatWorkAuthenticationConstants.URL_AUTHORIZE) + .redirectUri(WeChatWorkScanCodeLoginAuthenticationFilter.getLoginUrl(providerId)) + .state(DEFAULT_STATE_GENERATOR.generateKey()); + builder.parameters(parameters -> { + HashMap linkedParameters = new LinkedHashMap<>(); + parameters.forEach((key, value) -> { + if (OAuth2ParameterNames.CLIENT_ID.equals(key)) { + linkedParameters.put(WeChatWorkAuthenticationConstants.APP_ID, value); + } + if (OAuth2ParameterNames.STATE.equals(key)) { + linkedParameters.put(OAuth2ParameterNames.STATE, value); + } + if (OAuth2ParameterNames.REDIRECT_URI.equals(key)) { + linkedParameters.put(OAuth2ParameterNames.REDIRECT_URI, value); + } + }); + linkedParameters.put(WeChatWorkAuthenticationConstants.LOGIN_TYPE, + WeChatWorkAuthenticationConstants.JSSDK); + linkedParameters.put(WeChatWorkAuthenticationConstants.AGENT_ID, config.getAgentId()); + linkedParameters.put(WeChatWorkAuthenticationConstants.HREF, STYLE_BASE64); + parameters.clear(); + parameters.putAll(linkedParameters); + }); + this.sendRedirectForAuthorization(request, response, builder.build()); + } + + private void sendRedirectForAuthorization(HttpServletRequest request, + HttpServletResponse response, + OAuth2AuthorizationRequest authorizationRequest) throws IOException { + this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, + response); + this.authorizationRedirectStrategy.sendRedirect(request, response, + authorizationRequest.getAuthorizationRequestUri()); + } + + private final static String STYLE = "" + + ".impowerBox .qrcode {width: 280px;border: none;margin-top:10px;}\n" + + ".impowerBox .title {display: none;}\n" + + ".impowerBox .info {display: none;}\n" + + ".status_icon {display: none}\n" + + ".impowerBox .status {text-align: center;} "; + private final static String STYLE_BASE64 = "data:text/css;base64," + Base64.getEncoder() + .encodeToString(STYLE.getBytes(StandardCharsets.UTF_8)); + + public static RequestMatcher getRequestMatcher() { + return WECHAT_WORK_REQUEST_MATCHER; + } +} diff --git a/eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/filter/WeChatWorkScanCodeLoginAuthenticationFilter.java b/eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/filter/WeChatWorkScanCodeLoginAuthenticationFilter.java new file mode 100644 index 00000000..7df2cddd --- /dev/null +++ b/eiam-authentication/eiam-authentication-wechatwork/src/main/java/cn/topiam/employee/authentication/wechatwork/filter/WeChatWorkScanCodeLoginAuthenticationFilter.java @@ -0,0 +1,197 @@ +/* + * eiam-authentication-wechatwork - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.authentication.wechatwork.filter; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpMethod; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +import cn.topiam.employee.authentication.common.filter.AbstractIdpAuthenticationProcessingFilter; +import cn.topiam.employee.authentication.common.modal.IdpUser; +import cn.topiam.employee.authentication.common.service.UserIdpService; +import cn.topiam.employee.authentication.wechatwork.WeChatWorkIdpScanCodeConfig; +import cn.topiam.employee.authentication.wechatwork.constant.WeChatWorkAuthenticationConstants; +import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; +import cn.topiam.employee.core.context.ServerContextHelp; +import cn.topiam.employee.support.trace.TraceUtils; +import cn.topiam.employee.support.util.HttpClientUtils; +import static cn.topiam.employee.authentication.wechatwork.filter.WeChatWorkScanCodeAuthorizationRequestRedirectFilter.PROVIDER_ID; +import static cn.topiam.employee.common.enums.IdentityProviderType.WECHATWORK_SCAN_CODE; + +/** + * 企业微信扫码登录 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/8 21:11 + */ +@SuppressWarnings("DuplicatedCode") +public class WeChatWorkScanCodeLoginAuthenticationFilter extends + AbstractIdpAuthenticationProcessingFilter { + final String ERROR_CODE = "errcode"; + final String SUCCESS = "0"; + public final static String DEFAULT_FILTER_PROCESSES_URI = WECHATWORK_SCAN_CODE + .getLoginPathPrefix() + "/*"; + public static final AntPathRequestMatcher REQUEST_MATCHER = new AntPathRequestMatcher( + WECHATWORK_SCAN_CODE.getLoginPathPrefix() + "/" + "{" + PROVIDER_ID + "}", + HttpMethod.GET.name()); + + /** + * Creates a new instance + * + * @param identityProviderRepository the {@link IdentityProviderRepository} + * @param authenticationUserDetails {@link UserIdpService} + */ + public WeChatWorkScanCodeLoginAuthenticationFilter(IdentityProviderRepository identityProviderRepository, + UserIdpService authenticationUserDetails) { + super(DEFAULT_FILTER_PROCESSES_URI, authenticationUserDetails, identityProviderRepository); + } + + /** + * 企业微信认证 + * + * @param request {@link HttpServletRequest} + * @param response {@link HttpServletRequest} + * @return {@link HttpServletRequest} + * @throws AuthenticationException {@link AuthenticationException} AuthenticationException + */ + @Override + public Authentication attemptAuthentication(HttpServletRequest request, + HttpServletResponse response) throws AuthenticationException, + IOException { + OAuth2AuthorizationRequest authorizationRequest = getOAuth2AuthorizationRequest(request, + response); + TraceUtils.put(UUID.randomUUID().toString()); + RequestMatcher.MatchResult matcher = REQUEST_MATCHER.matcher(request); + Map variables = matcher.getVariables(); + String providerId = variables.get(PROVIDER_ID); + //code + String code = request.getParameter(OAuth2ParameterNames.CODE); + if (StringUtils.isEmpty(code)) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_CODE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + // state + String state = request.getParameter(OAuth2ParameterNames.STATE); + if (StringUtils.isEmpty(state)) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + if (!authorizationRequest.getState().equals(state)) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + //获取身份提供商 + IdentityProviderEntity provider = getIdentityProviderEntity(providerId); + WeChatWorkIdpScanCodeConfig config = JSONObject.parseObject(provider.getConfig(), + WeChatWorkIdpScanCodeConfig.class); + if (Objects.isNull(config)) { + logger.error("未查询到企业微信扫码登录配置"); + //无效身份提供商 + OAuth2Error oauth2Error = new OAuth2Error( + AbstractIdpAuthenticationProcessingFilter.INVALID_IDP_CONFIG); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + HashMap param = new HashMap<>(16); + // 获取user信息 + param.put(OAuth2ParameterNames.ACCESS_TOKEN, getToken(config)); + param.put(OAuth2ParameterNames.CODE, code); + JSONObject result = JSON.parseObject( + HttpClientUtils.get(WeChatWorkAuthenticationConstants.GET_USER_INFO, param)); + if (!Objects.equals(result.getString(ERROR_CODE), SUCCESS)) { + logger.error("获取企业微信用户个人信息失败: {}" + result.toJSONString()); + OAuth2Error oauth2Error = new OAuth2Error( + AbstractIdpAuthenticationProcessingFilter.GET_USERINFO_ERROR_CODE); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + } + // 返回 + String userId = StringUtils.defaultString(result.getString("UserId"), + result.getString("OpenId")); + IdpUser idpUser = IdpUser.builder().openId(userId).build(); + return attemptAuthentication(request, response, WECHATWORK_SCAN_CODE, providerId, idpUser); + } + + /** + * 获取token + * + * @param config {@link WeChatWorkIdpScanCodeConfig} + * @return {@link String} + */ + public String getToken(WeChatWorkIdpScanCodeConfig config) { + if (!Objects.isNull(cache)) { + cache.getIfPresent(OAuth2ParameterNames.ACCESS_TOKEN); + } + //获取access token + HashMap param = new HashMap<>(16); + param.put("corpid", config.getCorpId().trim()); + param.put("corpsecret", config.getAppSecret().trim()); + JSONObject result = JSON + .parseObject(HttpClientUtils.get(WeChatWorkAuthenticationConstants.GET_TOKEN, param)); + if (!Objects.equals(result.getString(ERROR_CODE), SUCCESS)) { + logger.error("获取access_token发生错误: {}" + result.toJSONString()); + throw new OAuth2AuthenticationException( + "获取access_token发生错误: " + result.toJSONString()); + } + //放入缓存 + cache = CacheBuilder.newBuilder() + .concurrencyLevel(Runtime.getRuntime().availableProcessors()) + .expireAfterWrite(result.getInteger(OAuth2ParameterNames.EXPIRES_IN), TimeUnit.SECONDS) + .build(); + cache.put(OAuth2ParameterNames.ACCESS_TOKEN, + result.getString(OAuth2ParameterNames.ACCESS_TOKEN)); + return cache.getIfPresent(OAuth2ParameterNames.ACCESS_TOKEN); + } + + /** + * 缓存 + */ + private Cache cache; + + public static String getLoginUrl(String providerId) { + String url = ServerContextHelp.getPortalPublicBaseUrl() + + WECHATWORK_SCAN_CODE.getLoginPathPrefix() + "/" + providerId; + return url.replaceAll("(?. + */ +package cn.topiam.employee.authentication.wechatwork; \ No newline at end of file diff --git a/eiam-authentication/pom.xml b/eiam-authentication/pom.xml new file mode 100644 index 00000000..7dffc6fe --- /dev/null +++ b/eiam-authentication/pom.xml @@ -0,0 +1,54 @@ + + + + + cn.topiam + eiam + 1.0.0-beta1 + ../pom.xml + + 4.0.0 + + eiam-authentication + pom + + eiam-authentication-core + eiam-authentication-dingtalk + eiam-authentication-feishu + eiam-authentication-qq + eiam-authentication-wechat + eiam-authentication-wechatwork + eiam-authentication-sms + eiam-authentication-mfa + eiam-authentication-all + + + + + + cn.topiam + eiam-core + ${project.version} + + + \ No newline at end of file diff --git a/eiam-common/pom.xml b/eiam-common/pom.xml new file mode 100644 index 00000000..59b2a869 --- /dev/null +++ b/eiam-common/pom.xml @@ -0,0 +1,62 @@ + + + + + cn.topiam + eiam + 1.0.0-beta1 + ../pom.xml + + jar + 4.0.0 + + eiam-common + + + cn.topiam + eiam-support + ${project.version} + + + + + + src/main/java + + ** + + + **/*.java + + false + + + src/main/resources + + + **/*.* + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/constants/AccountConstants.java b/eiam-common/src/main/java/cn/topiam/employee/common/constants/AccountConstants.java new file mode 100644 index 00000000..24309a43 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/constants/AccountConstants.java @@ -0,0 +1,79 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.constants; + +import static cn.topiam.employee.support.constant.EiamConstants.API_PATH; + +/** + * 账户常量 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/26 19:07 + */ +public final class AccountConstants { + + /** + * 用户API + */ + public final static String USER_PATH = API_PATH + "/user"; + /** + * 组织机构API + */ + public final static String ORGANIZATION_PATH = API_PATH + "/organization"; + /** + * 用户组API + */ + public final static String USER_GROUP_PATH = API_PATH + "/user_group"; + /** + * 身份源API + */ + public final static String IDENTITY_SOURCE_PATH = API_PATH + "/identity_source"; + + /** + * 组名称 + */ + public static final String ACCOUNT_API_DOC_GROUP_NAME = "系统账户"; + + /** + * ACCOUNT_API_PATHS + */ + public static final String[] ACCOUNT_API_PATHS = { USER_PATH + "/**", + ORGANIZATION_PATH + "/**", + USER_GROUP_PATH + "/**", + IDENTITY_SOURCE_PATH + "/**" }; + /** + * 身份源缓存 cacheName + */ + public static final String IDS_CACHE_NAME = "ids"; + + /** + * user 缓存 cacheName + */ + public static final String USER_CACHE_NAME = "user"; + + /** + * 根部门 ID + */ + public static final String ROOT_DEPT_ID = "root"; + + /** + * 根部门名称 + */ + public static final String ROOT_DEPT_NAME = "TopIAM Employee"; + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/constants/AnalysisConstants.java b/eiam-common/src/main/java/cn/topiam/employee/common/constants/AnalysisConstants.java new file mode 100644 index 00000000..c8026bef --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/constants/AnalysisConstants.java @@ -0,0 +1,39 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.constants; + +import static cn.topiam.employee.support.constant.EiamConstants.API_PATH; + +/** + * 统计分析API常量 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/26 19:07 + */ +public final class AnalysisConstants { + + /** + * 统计分析API路径 + */ + public final static String ANALYSIS_PATH = API_PATH + "/analysis"; + + /** + * 组名称 + */ + public static final String ANALYSIS_GROUP_NAME = "统计分析"; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/constants/AppConstants.java b/eiam-common/src/main/java/cn/topiam/employee/common/constants/AppConstants.java new file mode 100644 index 00000000..4cb74072 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/constants/AppConstants.java @@ -0,0 +1,50 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.constants; + +import static cn.topiam.employee.support.constant.EiamConstants.API_PATH; +import static cn.topiam.employee.support.constant.EiamConstants.COLON; + +/** + * 应用管理常量 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/26 19:07 + */ +public final class AppConstants { + + /** + * 应用管理API路径 + */ + public final static String APP_PATH = API_PATH + "/app"; + + /** + * 组名称 + */ + public static final String APP_GROUP_NAME = "应用管理"; + + /** + * APP 配置缓存前缀 + */ + public static final String APP_CACHE_NAME_PREFIX = "app" + COLON; + + /** + * APP 应用基本信息 + */ + public static final String APP_CACHE_NAME = APP_CACHE_NAME_PREFIX + "basic"; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/constants/AuditConstants.java b/eiam-common/src/main/java/cn/topiam/employee/common/constants/AuditConstants.java new file mode 100644 index 00000000..550532cc --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/constants/AuditConstants.java @@ -0,0 +1,50 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.constants; + +import static cn.topiam.employee.support.constant.EiamConstants.API_PATH; + +/** + * 系统审计常量 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/26 19:07 + */ +public final class AuditConstants { + /** + * 系统审计API路径 + */ + public final static String AUDIT_PATH = API_PATH + "/audit"; + + /** + * 组名称 + */ + public static final String AUDIT_GROUP_NAME = "行为审计"; + + /** + * 审计es index + */ + private static final String AUDIT_INDEX_PREFIX = "topiam-audit-"; + + public static String getAuditIndexPrefix(Boolean demoEnv) { + if (demoEnv) { + return AUDIT_INDEX_PREFIX + "demo-"; + } + return AUDIT_INDEX_PREFIX; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/constants/AuthenticationConstants.java b/eiam-common/src/main/java/cn/topiam/employee/common/constants/AuthenticationConstants.java new file mode 100644 index 00000000..33a053fe --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/constants/AuthenticationConstants.java @@ -0,0 +1,36 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.constants; + +import cn.topiam.employee.support.constant.EiamConstants; + +/** + * 认证常量 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/26 19:07 + */ +public final class AuthenticationConstants { + public final static String AUTHENTICATION_PATH = EiamConstants.API_PATH + + "/authentication"; + + /** + * 认证管理 + */ + public static final String AUTHENTICATION_GROUP_NAME = "认证管理"; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/constants/AuthorizeConstants.java b/eiam-common/src/main/java/cn/topiam/employee/common/constants/AuthorizeConstants.java new file mode 100644 index 00000000..013a47d4 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/constants/AuthorizeConstants.java @@ -0,0 +1,80 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.constants; + +import cn.topiam.employee.support.constant.EiamConstants; + +/** + * AuthorizeConstants + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/8 20:12 + */ +public final class AuthorizeConstants { + /** + * LOGIN + */ + public static final String LOGIN_PATH = EiamConstants.API_PATH + "/login"; + public final static String AUTHORIZE_PATH = EiamConstants.API_PATH + "/authorize"; + public static final String AUTHORIZATION_REQUEST_URI = EiamConstants.API_PATH + + "/authorization"; + /** + * form 表单登录 + */ + public static final String FORM_LOGIN = LOGIN_PATH; + /** + * sms login 路径 + */ + public static final String SMS_LOGIN = LOGIN_PATH + "/sms"; + + /** + * maf 验证 + */ + public static final String MFA_VALIDATE = LOGIN_PATH + "/mfa/validate"; + + /** + * mfa 登录提供者 + */ + public static final String LOGIN_MFA_FACTORS = LOGIN_PATH + "/mfa/factors"; + + /** + * 登录配置 + */ + public static final String LOGIN_CONFIG = LOGIN_PATH + "/config"; + + /** + * 发送OTP + */ + public static final String LOGIN_OTP_SEND = LOGIN_PATH + "/otp/send"; + + /** + * idp 绑定用户 路径 + */ + public static final String USER_BIND_IDP = LOGIN_PATH + "/idp_bind_user"; + + /** + * 前端登录路由 + */ + public static final String FE_LOGIN = "/login"; + + /** + * LOGOUT + */ + public static final String LOGOUT = EiamConstants.API_PATH + "/logout"; + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/constants/CaptchaConstants.java b/eiam-common/src/main/java/cn/topiam/employee/common/constants/CaptchaConstants.java new file mode 100644 index 00000000..0028994e --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/constants/CaptchaConstants.java @@ -0,0 +1,28 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.constants; + +/** + * 行为验证码常量 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/7/19 23:30 + */ +public final class CaptchaConstants { + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/constants/CommonConstants.java b/eiam-common/src/main/java/cn/topiam/employee/common/constants/CommonConstants.java new file mode 100644 index 00000000..94e1d970 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/constants/CommonConstants.java @@ -0,0 +1,45 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.constants; + +/** + * 通用配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/4/3 23:37 + */ +public final class CommonConstants { + /** + * 类型 + */ + public static final String TYPE = "@type"; + + /** + * 回调地址名称 + */ + public static final String CALLBACK_URL = "callbackUrl"; + /** + * 路径分隔符 + */ + public static final String PATH_SEPARATOR = "/"; + + /** + * 系统用户默认名称 + */ + public static final String SYSTEM_DEFAULT_USER_NAME = "topiam"; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/constants/ConfigBeanNameConstants.java b/eiam-common/src/main/java/cn/topiam/employee/common/constants/ConfigBeanNameConstants.java new file mode 100644 index 00000000..ca7c158a --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/constants/ConfigBeanNameConstants.java @@ -0,0 +1,61 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.constants; + +/** + * 配置Bean名称常量 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/3/23 22:53 + */ +public final class ConfigBeanNameConstants { + /** + * 任务执行者 + */ + public static final String TASK_EXECUTOR = "taskExecutor"; + /** + * 安全过滤器链 + */ + public static final String DEFAULT_SECURITY_FILTER_CHAIN = "defaultSecurityFilterChain"; + public static final String SOCIAL_SECURITY_FILTER_CHAIN = "socialSecurityFilterChain"; + public static final String SAML2_PROTOCOL_SECURITY_FILTER_CHAIN = "saml2ProtocolSecurityFilterChain"; + public static final String OIDC_PROTOCOL_SECURITY_FILTER_CHAIN = "oidcProtocolSecurityFilterChain"; + + /** + * 默认密码策略管理器 + */ + public static final String DEFAULT_PASSWORD_POLICY_MANAGER = "defaultPasswordPolicyManager"; + /** + * 验证码验证器 + */ + public static final String CAPTCHA_VALIDATOR = "captchaValidator"; + + /** + * 地理位置 + */ + public static final String GEO_LOCATION = "geoLocation"; + /** + * 短信提供商发送 + */ + public static final String SMS_PROVIDER_SEND = "smsProviderSend"; + /** + * 邮件提供商 + */ + public static final String MAIL_PROVIDER_SEND = "mailProviderSend"; + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/constants/ProtocolConstants.java b/eiam-common/src/main/java/cn/topiam/employee/common/constants/ProtocolConstants.java new file mode 100644 index 00000000..fe6b5132 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/constants/ProtocolConstants.java @@ -0,0 +1,147 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.constants; + +import lombok.Data; +import static com.nimbusds.openid.connect.sdk.op.OIDCProviderConfigurationRequest.OPENID_PROVIDER_WELL_KNOWN_PATH; + +import static cn.topiam.employee.common.constants.AppConstants.APP_CACHE_NAME_PREFIX; +import static cn.topiam.employee.common.constants.AuthorizeConstants.AUTHORIZE_PATH; + +/** + * Saml 常量 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/18 19:49 + */ +public final class ProtocolConstants { + /** + * 应用code + */ + public static final String APP_CODE = "appCode"; + /** + * 提供商变量 + */ + public static final String APP_CODE_VARIABLE = "{" + APP_CODE + "}"; + + /** + * 应用账户缓存名称 + */ + public static final String APP_ACCOUNT_CACHE_NAME = APP_CACHE_NAME_PREFIX + "account"; + + /** + * SAML2 配置缓存名称 + */ + public static final String SAML2_CONFIG_CACHE_NAME = APP_CACHE_NAME_PREFIX + "saml"; + + /** + * OIDC 配置缓存名称 + */ + public static final String OIDC_CONFIG_CACHE_NAME = APP_CACHE_NAME_PREFIX + "oidc"; + + /** + * APP Cert + */ + public static final String APP_CERT_CACHE_NAME = APP_CACHE_NAME_PREFIX + "cert"; + + /** + * OIDC Endpoint config + */ + @Data + public static class OidcEndpointConstants { + //@formatter:off + /** + * OIDC BASE 认证路径 + */ + public final static String OIDC_AUTHORIZE_BASE_PATH = AUTHORIZE_PATH + "/" + APP_CODE_VARIABLE; + + public final static String OIDC_AUTHORIZE_PATH = OIDC_AUTHORIZE_BASE_PATH +"/oidc"; + + public final static String OAUTH2_AUTHORIZE_PATH = OIDC_AUTHORIZE_BASE_PATH +"/oauth2"; + + /** + * OpenID Provider metadata. + */ + public static final String WELL_KNOWN_OPENID_CONFIGURATION = OIDC_AUTHORIZE_PATH +OPENID_PROVIDER_WELL_KNOWN_PATH; + + /** + * Jwk Set Endpoint + */ + public static final String JWK_SET_ENDPOINT = OIDC_AUTHORIZE_PATH + "/jwks"; + + /** + * OIDC Client Registration Endpoint + */ + public static final String OIDC_CLIENT_REGISTRATION_ENDPOINT = OIDC_AUTHORIZE_PATH + "/connect/register"; + + /** + * Authorization Endpoint + */ + public static final String AUTHORIZATION_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/auth"; + + /** + * Token Endpoint + */ + public static final String TOKEN_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/token"; + + /** + * Jwk Revocation Endpoint + */ + public static final String TOKEN_REVOCATION_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/revoke"; + + /** + * Token Introspection Endpoint + */ + public static final String TOKEN_INTROSPECTION_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/introspect"; + + /** + * OIDC User Info Endpoint + */ + public static final String OIDC_USER_INFO_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/userinfo"; + + //@formatter:on + } + + /** + * Saml2 Endpoint config + */ + @Data + public static class Saml2EndpointConstants { + /** + * SAML2 认证路径 + */ + public final static String SAML2_AUTHORIZE_BASE_PATH = AUTHORIZE_PATH + "/saml2/" + + APP_CODE_VARIABLE; + + /** + * SAML_METADATA_PATH + */ + public static final String SAML_METADATA_PATH = SAML2_AUTHORIZE_BASE_PATH + + "/metadata"; + /** + * SAML_LOGOUT_PATH + */ + public static final String SAML_LOGOUT_PATH = SAML2_AUTHORIZE_BASE_PATH + + "/logout"; + /** + * SAML_SSO_PATH + */ + public static final String SAML_SSO_PATH = SAML2_AUTHORIZE_BASE_PATH + "/sso"; + } + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/constants/SessionConstants.java b/eiam-common/src/main/java/cn/topiam/employee/common/constants/SessionConstants.java new file mode 100644 index 00000000..c00be432 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/constants/SessionConstants.java @@ -0,0 +1,41 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.constants; + +import cn.topiam.employee.support.constant.EiamConstants; + +/** + * SessionConstants + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/8 20:12 + */ +public final class SessionConstants { + public final static String SESSION_PATH = EiamConstants.API_PATH + "/session"; + + /** + * CURRENT_USER + */ + public static final String CURRENT_USER = SESSION_PATH + "/current_user"; + + /** + * session 当前状态 + */ + public static final String CURRENT_STATUS = SESSION_PATH + "/current_status"; + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/constants/SettingConstants.java b/eiam-common/src/main/java/cn/topiam/employee/common/constants/SettingConstants.java new file mode 100644 index 00000000..ceefb056 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/constants/SettingConstants.java @@ -0,0 +1,51 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.constants; + +import cn.topiam.employee.support.constant.EiamConstants; + +/** + * 审计常量 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/26 19:07 + */ +public final class SettingConstants { + public static final String NOT_CONFIG = "{}"; + + /** + * 系统设置API路径 + */ + public final static String SETTING_PATH = EiamConstants.API_PATH + "/setting"; + + /** + * 组名称 + */ + public static final String SETTING_GROUP_NAME = "系统设置"; + + /** + * 系统设置缓存 cacheName + */ + public static final String SETTING_CACHE_NAME = "setting"; + + /** + * admin 缓存 cacheName + */ + public static final String ADMIN_CACHE_NAME = "admin"; + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/constants/StorageConstants.java b/eiam-common/src/main/java/cn/topiam/employee/common/constants/StorageConstants.java new file mode 100644 index 00000000..f85a3103 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/constants/StorageConstants.java @@ -0,0 +1,43 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.constants; + +import cn.topiam.employee.support.constant.EiamConstants; + +/** + * 存储常量 + * + * @author TopIAM + */ +public final class StorageConstants { + + /** + * 存储API路径 + */ + public final static String STORAGE_PATH = EiamConstants.API_PATH + "/storage"; + + /** + * 文件存储 + */ + public static final String STORAGE_GROUP_NAME = "文件存储"; + + /** + * 存储缓存 cacheName + */ + public static final String STORAGE_CACHE_NAME = "storage"; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/MailSendRecordEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/MailSendRecordEntity.java new file mode 100644 index 00000000..b8132071 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/MailSendRecordEntity.java @@ -0,0 +1,90 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity; + +import java.time.LocalDateTime; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import cn.topiam.employee.common.enums.MailType; +import cn.topiam.employee.common.message.enums.MailProvider; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + * 邮件发送记录 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/8/1 19:41 + */ +@Entity +@Accessors(chain = true) +@Getter +@Setter +@ToString +@Table(name = "mail_send_record") +public class MailSendRecordEntity extends BaseEntity { + /** + * subject + */ + @Column(name = "subject_") + private String subject; + /** + * sender + */ + @Column(name = "sender_") + private String sender; + /** + * receiver + */ + @Column(name = "receiver_") + private String receiver; + /** + * content + */ + @Column(name = "content_") + private String content; + /** + * 消息类型 + */ + @Column(name = "type_") + private MailType type; + /** + * 平台 + */ + @Column(name = "provider_") + private MailProvider provider; + + /** + * 是否成功 + */ + @Column(name = "is_success") + private Boolean success; + + /** + * 发送时间 + */ + @Column(name = "send_time") + private LocalDateTime sendTime; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/SmsSendRecordEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/SmsSendRecordEntity.java new file mode 100644 index 00000000..9fdf09a0 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/SmsSendRecordEntity.java @@ -0,0 +1,96 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity; + +import java.time.LocalDateTime; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import cn.topiam.employee.common.enums.MessageCategory; +import cn.topiam.employee.common.enums.SmsType; +import cn.topiam.employee.common.message.enums.SmsProvider; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + * 短信记录发送表 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/8/1 19:41 + */ +@Entity +@Accessors(chain = true) +@Getter +@Setter +@ToString +@Table(name = "sms_send_record") +public class SmsSendRecordEntity extends BaseEntity { + /** + * phone_ + */ + @Column(name = "phone_") + private String phone; + + /** + * content + */ + @Column(name = "content_") + private String content; + + /** + * 短信类型 + */ + @Column(name = "type_") + private SmsType type; + + /** + * 消息分类 + */ + @Column(name = "category_") + private MessageCategory category; + + /** + * 平台 + */ + @Column(name = "provider_") + private SmsProvider provider; + + /** + * 是否成功 + */ + @Column(name = "is_success") + private Boolean success; + + /** + * 结果 + */ + @Column(name = "result_") + private String result; + + /** + * 发送时间 + */ + @Column(name = "send_time") + private LocalDateTime sendTime; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/OrganizationEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/OrganizationEntity.java new file mode 100644 index 00000000..863058f0 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/OrganizationEntity.java @@ -0,0 +1,143 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.account; + +import java.io.Serial; +import java.util.Objects; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import org.hibernate.Hibernate; + +import cn.topiam.employee.common.enums.DataOrigin; +import cn.topiam.employee.common.enums.OrganizationType; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + *

+ * 组织架构 + *

+ * + * @author TopIAM Automatic generated + * Created by support@topiam.cn on 2020-08-09 + */ +@Getter +@Setter +@ToString +@Entity +@Table(name = "`organization`") +public class OrganizationEntity extends BaseEntity { + + @Serial + private static final long serialVersionUID = 8143944323232082295L; + + /** + * 组织机构名称 + */ + @Column(name = "name_") + private String name; + + /** + * 机构编码 + */ + @Column(name = "code_") + private String code; + + /** + * 类型 + */ + @Column(name = "type_") + private OrganizationType type; + + /** + * 上级ID + */ + @Column(name = "parent_id") + private String parentId; + + /** + * 路径枚举ID + */ + @Column(name = "path_") + private String path; + + /** + * 路径显示名称 + */ + @Column(name = "display_path") + private String displayPath; + + /** + * 外部ID + */ + @Column(name = "external_id") + private String externalId; + + /** + * 数据来源 + */ + @Column(name = "data_origin") + private DataOrigin dataOrigin; + + /** + * 身份源id + */ + @Column(name = "identity_source_id") + private Long identitySourceId; + + /** + * 排序 + */ + @Column(name = "order_") + private Long order; + + /** + * 是否叶子节点 leaf + */ + @Column(name = "is_leaf") + private Boolean leaf; + + /** + * 是否启用 + */ + @Column(name = "is_enabled") + private Boolean enabled; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) { + return false; + } + OrganizationEntity entity = (OrganizationEntity) o; + return getId() != null && Objects.equals(getId(), entity.getId()); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/OrganizationMemberEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/OrganizationMemberEntity.java new file mode 100644 index 00000000..5720095b --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/OrganizationMemberEntity.java @@ -0,0 +1,90 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.account; + +import java.util.Objects; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import org.hibernate.Hibernate; + +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + * 组织机构成员 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/30 19:06 + */ +@Getter +@Setter +@ToString +@Accessors(chain = true) +@Entity +@Table(name = "`organization_member`") +public class OrganizationMemberEntity extends BaseEntity { + /** + * 组织机构ID + */ + @Column(name = "org_id") + private String orgId; + + /** + * 用户ID + */ + @Column(name = "user_id") + private Long userId; + + public OrganizationMemberEntity() { + } + + public OrganizationMemberEntity(String orgId, Long userId) { + this.orgId = orgId; + this.userId = userId; + } + + public OrganizationMemberEntity(Long id, String orgId, Long userId) { + super.setId(id); + this.orgId = orgId; + this.userId = userId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) { + return false; + } + OrganizationMemberEntity that = (OrganizationMemberEntity) o; + return getId() != null && Objects.equals(getId(), that.getId()); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserDetailEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserDetailEntity.java new file mode 100644 index 00000000..27cd0d48 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserDetailEntity.java @@ -0,0 +1,101 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.account; + +import java.io.Serial; +import java.util.Objects; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import org.hibernate.Hibernate; + +import cn.topiam.employee.common.enums.UserIdType; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + *

+ * 用户详情表 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-07 + */ +@Getter +@Setter +@ToString +@Accessors(chain = true) +@Entity +@Table(name = "user_detail") +public class UserDetailEntity extends BaseEntity { + + @Serial + private static final long serialVersionUID = -3599183663669763315L; + /** + * 用户表ID + */ + @Column(name = "user_id") + private Long userId; + + /** + * 证件类型 + */ + @Column(name = "id_type") + private UserIdType idType; + + /** + * 身份证号 + */ + @Column(name = "id_card") + private String idCard; + + /** + * 个人主页 + */ + @Column(name = "website_") + private String website; + + /** + * 地址 + */ + @Column(name = "address_") + private String address; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) { + return false; + } + UserDetailEntity that = (UserDetailEntity) o; + return getId() != null && Objects.equals(getId(), that.getId()); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserEntity.java new file mode 100644 index 00000000..1ce7ad8d --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserEntity.java @@ -0,0 +1,214 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.account; + +import java.io.Serial; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Objects; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.Transient; + +import org.hibernate.Hibernate; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import cn.topiam.employee.common.enums.DataOrigin; +import cn.topiam.employee.common.enums.UserStatus; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + *

+ * 用户表 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-07-31 22:10 + */ +@Getter +@Setter +@ToString +@Accessors(chain = true) +@Entity +@Table(name = "`user`") +public class UserEntity extends BaseEntity { + + @Serial + private static final long serialVersionUID = -2619231849746900857L; + + /** + * 用户名 + */ + @Column(name = "username_") + private String username; + + /** + * 密码 + */ + @Column(name = "password_") + private String password; + + /** + * 邮箱 + */ + @Column(name = "email_") + private String email; + + /** + * 手机号 + */ + @Column(name = "phone_") + private String phone; + + /** + * 手机号 + */ + @Column(name = "phone_area_code") + private String phoneAreaCode; + + /** + * 姓名 + */ + @Column(name = "full_name") + private String fullName; + + /** + * 昵称 + */ + @Column(name = "nick_name") + private String nickName; + + /** + * 头像URL + */ + @Column(name = "avatar_") + private String avatar; + + /** + * 状态 ENABLE:启用 DISABLE:禁用 LOCKING:锁定 + */ + @Column(name = "status_") + private UserStatus status; + + /** + * 数据来源 + */ + @Column(name = "data_origin") + private DataOrigin dataOrigin; + + /** + * 身份源ID + */ + @Column(name = "identity_source_id") + private Long identitySourceId; + + /** + * 邮箱验证有效 + */ + @Column(name = "email_verified") + private Boolean emailVerified; + + /** + * 手机有效 + */ + @Column(name = "phone_verified") + private Boolean phoneVerified; + + /** + * 共享秘钥-TIME OTP + */ + @Column(name = "shared_secret") + private String sharedSecret; + + /** + * 是否绑定 TOTP + */ + @Column(name = "totp_bind") + private Boolean totpBind; + + /** + * 认证次数 + */ + @Column(name = "auth_total") + private Long authTotal; + /** + * 上次认证IP + */ + @Column(name = "last_auth_ip") + private String lastAuthIp; + /** + * 上次认证时间 + */ + @Column(name = "last_auth_time") + private LocalDateTime lastAuthTime; + + /** + * 扩展参数 + */ + @Column(name = "expand_") + private String expand; + + /** + * 外部ID + */ + @Column(name = "external_id") + private String externalId; + /** + * 过期时间 + */ + @Column(name = "expire_date") + private LocalDate expireDate; + + /** + * 最后修改密码时间 + */ + @Column(name = "last_update_password_time") + private LocalDateTime lastUpdatePasswordTime; + + /** + * 暂存密码(明文) + */ + @Transient + @JsonIgnore + private String plaintext; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) { + return false; + } + UserEntity user = (UserEntity) o; + return getId() != null && Objects.equals(getId(), user.getId()); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserGroupEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserGroupEntity.java new file mode 100644 index 00000000..d34e54b0 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserGroupEntity.java @@ -0,0 +1,62 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.account; + +import java.io.Serial; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + *

+ * 用户组表 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-07-31 + */ +@Getter +@Setter +@ToString +@Accessors(chain = true) +@Entity +@Table(name = "`user_group`") +public class UserGroupEntity extends BaseEntity { + + @Serial + private static final long serialVersionUID = -2619231849746900857L; + /** + * 用户名 + */ + @Column(name = "name_") + private String name; + + /** + * 密码 + */ + @Column(name = "code_") + private String code; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserGroupMemberEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserGroupMemberEntity.java new file mode 100644 index 00000000..2d00b12c --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserGroupMemberEntity.java @@ -0,0 +1,54 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.account; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + * 用户组成员 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/30 19:04 + */ +@Getter +@Setter +@ToString +@Accessors(chain = true) +@Entity +@Table(name = "`user_group_member`") +public class UserGroupMemberEntity extends BaseEntity { + /** + * 组ID + */ + @Column(name = "group_id") + private Long groupId; + /** + * 用户ID + */ + @Column(name = "user_id") + private Long userId; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserHistoryPasswordEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserHistoryPasswordEntity.java new file mode 100644 index 00000000..e8da0fa1 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserHistoryPasswordEntity.java @@ -0,0 +1,72 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.account; + +import java.io.Serial; +import java.time.LocalDateTime; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + *

+ * 用户历史密码表 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-07-31 + */ +@Getter +@Setter +@ToString +@Accessors(chain = true) +@Entity +@Table(name = "`user_history_password`") +public class UserHistoryPasswordEntity extends BaseEntity { + + @Serial + private static final long serialVersionUID = -2619231849746900857L; + /** + * 用户名 + */ + @Column(name = "user_id") + private String userId; + + /** + * 密码 + */ + @Column(name = "password_") + @JsonIgnore + private String password; + + /** + * 更改时间 + */ + @Column(name = "change_time") + private LocalDateTime changeTime; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserIdpBindEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserIdpBindEntity.java new file mode 100644 index 00000000..459fd305 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/UserIdpBindEntity.java @@ -0,0 +1,86 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.account; + +import java.io.Serial; +import java.time.LocalDateTime; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import cn.topiam.employee.common.enums.IdentityProviderType; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + * 用户认证方式绑定表 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/29 20:23 + */ +@Entity +@Table(name = "user_idp_bind") +@Accessors(chain = true) +@Getter +@Setter +@ToString +public class UserIdpBindEntity extends BaseEntity { + @Serial + private static final long serialVersionUID = -14364708756807242L; + + /** + * 用户ID + */ + @Column(name = "user_id") + private Long userId; + + /** + * OpenId + */ + @Column(name = "open_id") + private String openId; + + /** + * 身份提供商 ID + */ + @Column(name = "idp_id") + private String idpId; + + /** + * 身份提供商 类型 + */ + @Column(name = "idp_type") + private IdentityProviderType idpType; + + /** + * 绑定时间 + */ + @Column(name = "bind_time") + private LocalDateTime bindTime; + + /** + * 附加信息 + */ + @Column(name = "addition_info") + private String additionInfo; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/po/UserIdpBindPo.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/po/UserIdpBindPo.java new file mode 100644 index 00000000..87c994ca --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/po/UserIdpBindPo.java @@ -0,0 +1,44 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.account.po; + +import cn.topiam.employee.common.entity.account.UserIdpBindEntity; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户身份提供商绑定 PO + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/10 22:46 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class UserIdpBindPo extends UserIdpBindEntity { + + /** + * 用户名称 + */ + private String userName; + + /** + * 身份认证提供商名称 + */ + private String idpName; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/po/UserPO.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/po/UserPO.java new file mode 100644 index 00000000..116befd0 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/po/UserPO.java @@ -0,0 +1,44 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.account.po; + +import java.io.Serial; + +import cn.topiam.employee.common.entity.account.UserEntity; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户 PO + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/10 22:46 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class UserPO extends UserEntity { + + @Serial + private static final long serialVersionUID = 2330202241971348786L; + + /** + * 组织机构显示目录 + */ + private String orgDisplayPath; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/query/UserGroupMemberListQuery.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/query/UserGroupMemberListQuery.java new file mode 100644 index 00000000..bdb8fd2f --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/query/UserGroupMemberListQuery.java @@ -0,0 +1,55 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.account.query; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotEmpty; + +import org.springdoc.api.annotations.ParameterObject; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 查询用户列表入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:08 + */ +@Data +@Schema(description = "查询用户组成员列表入参") +@ParameterObject +public class UserGroupMemberListQuery implements Serializable { + @Serial + private static final long serialVersionUID = -7110595216804896858L; + /** + * 组ID + */ + @NotEmpty(message = "组ID不能为空") + @Parameter(description = "组ID") + private String id; + /** + * 用户名 + */ + @Parameter(description = "用户名") + private String username; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/query/UserListNotInGroupQuery.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/query/UserListNotInGroupQuery.java new file mode 100644 index 00000000..26d80569 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/query/UserListNotInGroupQuery.java @@ -0,0 +1,55 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.account.query; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotEmpty; + +import org.springdoc.api.annotations.ParameterObject; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 查询用户列表(不在指定用户组)入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:08 + */ +@Data +@Schema(description = "查询用户列表(不在指定用户组)入参") +@ParameterObject +public class UserListNotInGroupQuery implements Serializable { + @Serial + private static final long serialVersionUID = -7110595216804896858L; + /** + * 组ID + */ + @NotEmpty(message = "组ID不能为空") + @Parameter(description = "组ID") + private String id; + /** + * 关键字 + */ + @Parameter(description = "关键字") + private String keyword; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/query/UserListQuery.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/query/UserListQuery.java new file mode 100644 index 00000000..1e416485 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/account/query/UserListQuery.java @@ -0,0 +1,93 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.account.query; + +import java.io.Serial; +import java.io.Serializable; + +import org.springdoc.api.annotations.ParameterObject; + +import cn.topiam.employee.common.enums.DataOrigin; +import cn.topiam.employee.common.enums.UserStatus; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 查询用户列表入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:08 + */ +@Data +@Schema(description = "查询用户列表入参") +@ParameterObject +public class UserListQuery implements Serializable { + @Serial + private static final long serialVersionUID = -7110595216804896858L; + + /** + * 用户名 + */ + @Parameter(description = "用户名") + private String username; + + /** + * 姓名 + */ + @Parameter(description = "姓名") + private String fullName; + + /** + * 邮箱 + */ + @Parameter(description = "邮箱") + private String email; + + /** + * 手机号 + */ + @Parameter(description = "手机号") + private String phone; + + /** + * 组织ID + */ + @Parameter(description = "组织ID") + private String organizationId; + + /** + * 包含子组织 + */ + @Parameter(description = "包含子组织") + private Boolean inclSubOrganization; + + /** + * 状态 + */ + @Parameter(description = "状态") + private UserStatus status; + + /** + * 数据来源 + */ + @Parameter(description = "数据来源") + private DataOrigin dataOrigin; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/analysis/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/analysis/package-info.java new file mode 100644 index 00000000..fe66efe5 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/analysis/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.analysis; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppAccessPolicyEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppAccessPolicyEntity.java new file mode 100644 index 00000000..64f2c4a1 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppAccessPolicyEntity.java @@ -0,0 +1,62 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.app; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import cn.topiam.employee.common.enums.PolicySubjectType; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + * 应用授权策略 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/4 19:29 + */ +@Getter +@Setter +@ToString +@Entity +@Accessors(chain = true) +@Table(name = "app_access_policy") +public class AppAccessPolicyEntity extends BaseEntity { + /** + * 应用ID + */ + @Column(name = "app_id") + private Long appId; + + /** + * 主体ID(用户、分组、组织机构) + */ + @Column(name = "subject_id") + private String subjectId; + + /** + * 主体类型(用户、分组、组织机构) + */ + @Column(name = "subject_type") + private PolicySubjectType subjectType; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppAccountEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppAccountEntity.java new file mode 100644 index 00000000..73f7c493 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppAccountEntity.java @@ -0,0 +1,61 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.app; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + * 应用账户 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/31 20:51 + */ +@Getter +@Setter +@ToString +@Entity +@Accessors(chain = true) +@Table(name = "app_account") +public class AppAccountEntity extends BaseEntity { + /** + * 应用ID + */ + @Column(name = "app_id") + private Long appId; + + /** + * 用户ID + */ + @Column(name = "user_id") + private Long userId; + + /** + * 账户名称 + */ + @Column(name = "account_") + private String account; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppCertEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppCertEntity.java new file mode 100644 index 00000000..7bc0e58b --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppCertEntity.java @@ -0,0 +1,123 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.app; + +import java.math.BigInteger; +import java.time.LocalDateTime; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import cn.topiam.employee.common.enums.app.AppCertUsingType; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/5/31 20:51 + */ +@Getter +@Setter +@ToString +@Entity +@Accessors(chain = true) +@Table(name = "app_cert") +public class AppCertEntity extends BaseEntity { + /** + * 应用ID + */ + @Column(name = "app_id") + private Long appId; + + /** + * 证书序列号 + */ + @Column(name = "serial_") + private BigInteger serial; + + /** + * 主题信息 + */ + @Column(name = "subject_") + private String subject; + + /** + * 签发者信息 + */ + @Column(name = "issuer_") + private String issuer; + + /** + * 开始时间 + */ + @Column(name = "begin_date") + private LocalDateTime beginDate; + + /** + * 结束时间 + */ + @Column(name = "end_date") + private LocalDateTime endDate; + + /** + * 有效天数 + */ + @Column(name = "validity_") + private Integer validity; + + /** + * 算法 + */ + @Column(name = "sign_algo") + private String signAlgo; + + /** + * 私钥长度 + */ + @Column(name = "key_long") + private Integer keyLong; + + /** + * 私钥 + */ + @Column(name = "private_key") + private String privateKey; + + /** + * 公钥 + */ + @Column(name = "public_key") + private String publicKey; + + /** + * 证书 + */ + @Column(name = "cert_") + private String cert; + + /** + * 使用类型 + */ + @Column(name = "using_type") + private AppCertUsingType usingType; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppEntity.java new file mode 100644 index 00000000..5c17d824 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppEntity.java @@ -0,0 +1,119 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.app; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import cn.topiam.employee.common.enums.app.AppProtocol; +import cn.topiam.employee.common.enums.app.AppType; +import cn.topiam.employee.common.enums.app.AuthorizationType; +import cn.topiam.employee.common.enums.app.InitLoginType; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + * 应用 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/11 19:07 + */ +@Getter +@Setter +@ToString +@Entity +@Accessors(chain = true) +@Table(name = "app") +public class AppEntity extends BaseEntity { + + /** + * 应用名称 + */ + @Column(name = "name_") + private String name; + + /** + * 唯一CODE 不可修改 + */ + @Column(name = "code_") + private String code; + + /** + * 客户端ID + */ + @Column(name = "client_id") + private String clientId; + /** + * 客户端秘钥 + */ + @Column(name = "client_secret") + private String clientSecret; + + /** + * 模板 + */ + @Column(name = "template_") + private String template; + + /** + * 协议 + */ + @Column(name = "protocol_") + private AppProtocol protocol; + + /** + * 应用类型 + */ + @Column(name = "type_") + private AppType type; + + /** + * 应用图标 + */ + @Column(name = "icon_") + private String icon; + + /** + * SSO 发起登录类型 + */ + @Column(name = "init_login_type") + private InitLoginType initLoginType; + + /** + * SSO 发起登录URL + */ + @Column(name = "init_login_url") + private String initLoginUrl; + + /** + * SSO 授权类型 + */ + @Column(name = "authorization_type") + private AuthorizationType authorizationType; + + /** + * 是否启用 + */ + @Column(name = "is_enabled") + private Boolean enabled; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppOidcConfigEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppOidcConfigEntity.java new file mode 100644 index 00000000..ca9a3e2d --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppOidcConfigEntity.java @@ -0,0 +1,143 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.app; + +import java.util.Set; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; + +import com.vladmihalcea.hibernate.type.json.JsonStringType; + +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + * APP OIDC 配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/22 22:31 + */ +@Getter +@Setter +@ToString +@Entity +@Accessors(chain = true) +@Table(name = "app_oidc_config") +@TypeDef(name = "json", typeClass = JsonStringType.class) +public class AppOidcConfigEntity extends BaseEntity { + + /** + * APP ID + */ + @Column(name = "app_id") + private Long appId; + + /** + * 客户端认证方式 + */ + @Column(name = "client_auth_methods") + @Type(type = "json") + private Set clientAuthMethods; + /** + * 授权类型 + */ + @Column(name = "auth_grant_types") + @Type(type = "json") + private Set authGrantTypes; + + /** + * 响应类型 + */ + @Column(name = "response_types") + @Type(type = "json") + private Set responseTypes; + + /** + * 重定向URIs + */ + @Column(name = "redirect_uris") + @Type(type = "json") + private Set redirectUris; + /** + * scopes + */ + @Column(name = "grant_scopes") + @Type(type = "json") + private Set grantScopes; + /** + * 是否需要授权同意 + */ + @Column(name = "require_auth_consent") + private Boolean requireAuthConsent; + /** + * 需要PKCE + */ + @Column(name = "require_proof_key") + private Boolean requireProofKey; + + /** + * 令牌 Endpoint 身份验证签名算法 + */ + @Column(name = "token_endpoint_auth_signing_algorithm") + private String tokenEndpointAuthSigningAlgorithm; + + /** + * 刷新 Token生存时间(分钟) + */ + @Column(name = "refresh_token_time_to_live") + private Integer refreshTokenTimeToLive; + + /** + * ID Token生存时间(分钟) + */ + @Column(name = "id_token_time_to_live") + private Integer idTokenTimeToLive; + + /** + * 访问 Token生存时间(分钟) + */ + @Column(name = "access_token_time_to_live") + private Integer accessTokenTimeToLive; + + /** + * Id Token 签名算法 + */ + @Column(name = "id_token_signature_algorithm") + private String idTokenSignatureAlgorithm; + + /** + * Access Token 格式 + */ + @Column(name = "access_token_format") + private String accessTokenFormat; + + /** + * 是否重用刷新令牌 + */ + @Column(name = "reuse_refresh_token") + private Boolean reuseRefreshToken; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppPermissionActionEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppPermissionActionEntity.java new file mode 100644 index 00000000..9638e1db --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppPermissionActionEntity.java @@ -0,0 +1,72 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.app; + +import java.io.Serial; + +import javax.persistence.*; + +import cn.topiam.employee.common.enums.PermissionActionType; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + * 应用权限 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/2 21:05 + */ +@Getter +@Setter +@ToString +@Entity +@Accessors(chain = true) +@Table(name = "`app_permission_action`") +public class AppPermissionActionEntity extends BaseEntity { + + @Serial + private static final long serialVersionUID = -3954680915360748087L; + + /** + * 权限值 + */ + @Column(name = "value_") + private String value; + /** + * 描述 + */ + @Column(name = "name_") + private String name; + + /** + * 权限类型 + */ + @Column(name = "type_") + private PermissionActionType type; + + /** + * 资源 + */ + @ManyToOne + @JoinColumn(name = "resource_id") + private AppPermissionResourceEntity resource; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppPermissionPolicyEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppPermissionPolicyEntity.java new file mode 100644 index 00000000..9df5ea1f --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppPermissionPolicyEntity.java @@ -0,0 +1,79 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.app; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import cn.topiam.employee.common.enums.PolicyEffect; +import cn.topiam.employee.common.enums.PolicyObjectType; +import cn.topiam.employee.common.enums.PolicySubjectType; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + * 应用策略 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/4 19:41 + */ +@Getter +@Setter +@ToString +@Entity +@Accessors(chain = true) +@Table(name = "`app_permission_policy`") +public class AppPermissionPolicyEntity extends BaseEntity { + + /** + * 应用id + */ + @Column(name = "app_id") + private Long appId; + + /** + * 权限主体ID(用户、角色、分组、组织机构) + */ + @Column(name = "subject_id") + private String subjectId; + /** + * 权限主体类型(用户、角色、分组、组织机构) + */ + @Column(name = "subject_type") + private PolicySubjectType subjectType; + /** + * 权限客体ID(权限、角色) + */ + @Column(name = "object_id") + private Long objectId; + /** + * 权限客体类型(权限、角色) + */ + @Column(name = "object_type") + private PolicyObjectType objectType; + /** + * Effect + */ + @Column(name = "effect_") + private PolicyEffect effect; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppPermissionResourceEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppPermissionResourceEntity.java new file mode 100644 index 00000000..74773979 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppPermissionResourceEntity.java @@ -0,0 +1,89 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.app; + +import java.io.Serial; +import java.util.List; + +import javax.persistence.*; + +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; +import static javax.persistence.FetchType.LAZY; + +/** + *

+ * 应用资源关联 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-10 + */ +@Getter +@Setter +@ToString +@Entity +@Accessors(chain = true) +@Table(name = "`app_permission_resource`") +public class AppPermissionResourceEntity extends BaseEntity { + + @Serial + private static final long serialVersionUID = 7342074686605139968L; + + /** + * 资源编码 + */ + @Column(name = "code_") + private String code; + + /** + * 资源名称 + */ + @Column(name = "name_") + private String name; + + /** + * 应用ID + */ + @Column(name = "app_id") + private Long appId; + + /** + * 描述 + */ + @Column(name = "desc_") + private String desc; + + /** + * 是否启用 + */ + @Column(name = "is_enabled") + private Boolean enabled; + + /** + * 权限 + */ + @ToString.Exclude + @OneToMany(mappedBy = "resource", fetch = LAZY, cascade = { CascadeType.PERSIST, + CascadeType.REMOVE }) + private List actions; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppPermissionRoleEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppPermissionRoleEntity.java new file mode 100644 index 00000000..f6cf0769 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppPermissionRoleEntity.java @@ -0,0 +1,75 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.app; + +import java.io.Serial; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + *

+ * 应用角色表 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-10 + */ +@Getter +@Setter +@ToString +@Entity +@Accessors(chain = true) +@Table(name = "`app_permission_role`") +public class AppPermissionRoleEntity extends BaseEntity { + + @Serial + private static final long serialVersionUID = -7761332532995424593L; + + /** + * 角色名称 + */ + @Column(name = "name_") + private String name; + + /** + * 角色编码 + */ + @Column(name = "code_") + private String code; + + /** + * 应用ID + */ + @Column(name = "app_id") + private Long appId; + + /** + * 是否启用 + */ + @Column(name = "is_enabled") + private Boolean enabled; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppSaml2ConfigEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppSaml2ConfigEntity.java new file mode 100644 index 00000000..29b16b41 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppSaml2ConfigEntity.java @@ -0,0 +1,248 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.app; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.vladmihalcea.hibernate.type.json.JsonStringType; + +import cn.topiam.employee.common.enums.app.*; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + * APP SAML 配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/22 22:31 + */ +@Getter +@Setter +@ToString +@Entity +@Accessors(chain = true) +@Table(name = "app_saml2_config") +@TypeDef(name = "json", typeClass = JsonStringType.class) +public class AppSaml2ConfigEntity extends BaseEntity { + /** + * APP ID + */ + @Column(name = "app_id") + private Long appId; + + /** + * SP 元数据 + */ + @Column(name = "sp_metadata") + private String spMetadata; + + /** + * SpEntityId + */ + @Column(name = "sp_entity_id") + private String spEntityId; + + /** + * SP 单点登录 ACS URL + */ + @Column(name = "sp_acs_url") + private String spAcsUrl; + + /** + * SP 单点登出 URL + */ + @Column(name = "sp_slo_url") + private String spSloUrl; + + /** + * 是否对 SAML Request 签名进行验证 ,用来对SAML Request签名进行验证,对应SP元数据文件中“AuthnRequestsSigned”值 + */ + @Column(name = "sp_requests_signed") + private Boolean spRequestsSigned; + + /** + * SP公钥证书,用来验证SAML request的签名,对应SP元数据文件中 use='signing' 证书内容 + */ + @Column(name = "sp_sign_cert") + private String spSignCert; + + /** + * Recipient(收件人) + * + * Recipient 是指定 TopIAM 可以向其呈现断言的实体或位置的URI。 + * 此属性可以指定应将断言传递到的收件人端点。此属性有助于防止中介机构将断言重定向到其他端点。 + * 默认情况下,TopIAM 发送ACS URL作为收件人值。 + */ + @Column(name = "recipient_") + private String recipient; + + /** + * 目标受众 + * + * 指定此SAML断言的目标受众,默认和SP Entity ID相同。 + * 通常是 URL,但在技术上可以格式化为任何数据字符串 + */ + @Column(name = "audience_") + private String audience; + + /** + * 单点登录 ACS BINDING + */ + @Column(name = "acs_binding") + private String acsBinding; + + /** + * 单点登出 SLS BINDING + */ + @Column(name = "sls_binding") + private String slsBinding; + + /** + * SAML Response 中指定账户标识 NameID 字段格式。一般无需修改。 + */ + @Column(name = "nameid_format") + private SamlNameIdFormatType nameIdFormat; + + /** + * NameId + */ + @Column(name = "nameid_value_type") + private SamlNameIdValueType nameIdValueType; + + /** + * 签名断言 + */ + @Column(name = "assert_signed") + private Boolean assertSigned; + + /** + * 断言签名使用的非对称算法 + */ + @Column(name = "assert_sign_algorithm") + private SamlSignAssertAlgorithmType assertSignAlgorithm; + + /** + * 加密断言 + */ + @Column(name = "assert_encrypted") + private Boolean assertEncrypted; + + /** + * 断言加密使用的非对称算法 + */ + @Column(name = "assert_encrypt_algorithm") + private SamlEncryptAssertAlgorithmType assertEncryptAlgorithm; + + /** + * 响应是否加密 + */ + @Column(name = "response_signed") + private Boolean responseSigned; + + /** + * 响应签名使用的非对称算法 + */ + @Column(name = "response_sign_algorithm") + private SamlSignAssertAlgorithmType responseSignAlgorithm; + + /** + * SAML 身份认证上下文 + */ + @Column(name = "authn_context_classref") + private AuthnContextClassRefType authnContextClassRef; + + /** + * IDP 发起 SSO 登录成功后,应用应自动跳转的地址。在 SAML Response 中会在 RelayState 参数中传递,应用读取后实现跳转。 + */ + @Column(name = "relay_state") + private String relayState; + + /** + * 在 SAML Response 中,可以将额外用户字段(例如邮箱、显示名等)返回给应用解析。 + */ + @Type(type = "json") + @Column(name = "attribute_statements") + private List attributeStatements; + + /** + * 模版配置 + */ + @Type(type = "json") + @Column(name = "additional_config") + private Map additionalConfig; + + @Data + public static class AttributeStatement implements Serializable { + + @Serial + private static final long serialVersionUID = -7186912435615904210L; + + /** + * key + */ + private String key; + + /** + * 名称 + */ + private String name; + + /** + * nameFormat + */ + private AttributeNameFormat nameFormat; + + /** + * value + */ + @JsonAlias({ "value", "valueExpression" }) + private String valueExpression; + + public AttributeStatement() { + } + + public AttributeStatement(String name, String valueExpression) { + this.name = name; + this.valueExpression = valueExpression; + this.nameFormat = AttributeNameFormat.UNSPECIFIED; + } + + public AttributeStatement(String name, AttributeNameFormat nameFormat, + String valueExpression) { + this.name = name; + this.valueExpression = valueExpression; + this.nameFormat = nameFormat; + } + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppAccessPolicyPO.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppAccessPolicyPO.java new file mode 100644 index 00000000..d0458de6 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppAccessPolicyPO.java @@ -0,0 +1,60 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.app.po; + +import cn.topiam.employee.common.entity.app.AppAccessPolicyEntity; +import cn.topiam.employee.common.enums.app.AppProtocol; +import cn.topiam.employee.common.enums.app.AppType; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 应用授权策略po + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/10 22:46 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class AppAccessPolicyPO extends AppAccessPolicyEntity { + /** + * 应用名称 + */ + private String appName; + + /** + * 模板 + */ + private String appTemplate; + + /** + * 协议 + */ + private AppProtocol appProtocol; + + /** + * 应用类型 + */ + private AppType appType; + + /** + * 授权主体 + */ + private String subjectName; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppAccountPO.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppAccountPO.java new file mode 100644 index 00000000..6bfcc3f8 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppAccountPO.java @@ -0,0 +1,61 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.app.po; + +import cn.topiam.employee.common.entity.app.AppAccountEntity; +import cn.topiam.employee.common.enums.app.AppProtocol; +import cn.topiam.employee.common.enums.app.AppType; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 应用账户po + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/10 22:46 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class AppAccountPO extends AppAccountEntity { + + /** + * 用户名称 + */ + private String username; + + /** + * 应用名称 + */ + private String appName; + + /** + * 模板 + */ + private String appTemplate; + + /** + * 协议 + */ + private AppProtocol appProtocol; + + /** + * 应用类型 + */ + private AppType appType; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppOidcConfigPO.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppOidcConfigPO.java new file mode 100644 index 00000000..69ba289a --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppOidcConfigPO.java @@ -0,0 +1,75 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.app.po; + +import cn.topiam.employee.common.entity.app.AppOidcConfigEntity; +import cn.topiam.employee.common.enums.app.AuthorizationType; +import cn.topiam.employee.common.enums.app.InitLoginType; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/23 20:45 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class AppOidcConfigPO extends AppOidcConfigEntity { + + /** + * 应用编码 + */ + private String appCode; + + /** + * 模版 + */ + private String appTemplate; + + /** + * 客户端ID + */ + private String clientId; + + /** + * 客户端秘钥 + */ + private String clientSecret; + + /** + * SSO 发起方 + */ + private InitLoginType initLoginType; + + /** + * SSO 登录链接 + */ + private String initLoginUrl; + + /** + * 授权范围 + */ + private AuthorizationType authorizationType; + + /** + * 应用是否启用 + */ + private Boolean enabled; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppPermissionPolicyPO.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppPermissionPolicyPO.java new file mode 100644 index 00000000..d2838bab --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppPermissionPolicyPO.java @@ -0,0 +1,93 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.app.po; + +import java.io.Serial; +import java.io.Serializable; + +import cn.topiam.employee.common.enums.PolicyEffect; +import cn.topiam.employee.common.enums.PolicyObjectType; +import cn.topiam.employee.common.enums.PolicySubjectType; + +import lombok.Data; +import lombok.experimental.Accessors; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 策略分页查询结果 + * + * @author TopIAM + */ +@Data +@Accessors(chain = true) +@Schema(description = "分页查询策略结果") +public class AppPermissionPolicyPO implements Serializable { + + @Serial + private static final long serialVersionUID = 3320953184046791392L; + + /** + * 授权主体名称 + */ + @Parameter(description = "授权主体名称") + private String subjectName; + + /** + * 权限客体名菜 + */ + @Parameter(description = "授权客体名称") + private String objectName; + + /** + * ID + */ + @Parameter(description = "id") + private Long id; + + /** + * 授权主体id + */ + @Parameter(description = "授权主体id") + private String subjectId; + + /** + * 权限主体类型(用户、角色、分组、组织机构) + */ + @Parameter(description = "授权主体类型") + private PolicySubjectType subjectType; + + /** + * 权限客体ID + */ + @Parameter(description = "授权客体id") + private Long objectId; + + /** + * 权限客体类型(权限、角色) + */ + @Parameter(description = "授权客体类型") + private PolicyObjectType objectType; + + /** + * 授权作用 + */ + @Parameter(description = "授权作用") + private PolicyEffect effect; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppSaml2ConfigPO.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppSaml2ConfigPO.java new file mode 100644 index 00000000..98db1811 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppSaml2ConfigPO.java @@ -0,0 +1,81 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.app.po; + +import org.opensaml.security.credential.Credential; + +import cn.topiam.employee.common.entity.app.AppSaml2ConfigEntity; +import cn.topiam.employee.common.enums.app.AuthorizationType; +import cn.topiam.employee.common.enums.app.InitLoginType; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/23 20:45 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class AppSaml2ConfigPO extends AppSaml2ConfigEntity { + /** + * APP CODE + */ + private String appCode; + + /** + * 模版 + */ + private String appTemplate; + + /** + * 客户端ID + */ + private String clientId; + + /** + * 客户端秘钥 + */ + private String clientSecret; + + /** + * SSO 发起方 + */ + private InitLoginType initLoginType; + + /** + * SSO 登录链接 + */ + private String initLoginUrl; + + /** + * 授权范围 + */ + private AuthorizationType authorizationType; + + /** + * IDP 签名证书 + */ + private Credential idpSignCert; + + /** + * IDP 加密证书 + */ + private Credential idpEncryptCert; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/query/AppAccessPolicyQuery.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/query/AppAccessPolicyQuery.java new file mode 100644 index 00000000..fa552384 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/query/AppAccessPolicyQuery.java @@ -0,0 +1,69 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.app.query; + +import org.springdoc.api.annotations.ParameterObject; + +import cn.topiam.employee.common.enums.PolicySubjectType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 应用授权策略查询参数 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/9/27 19:29 + */ +@Data +@Schema(description = "应用授权策略查询参数") +@ParameterObject +public class AppAccessPolicyQuery { + + /** + * 应用id + */ + @Parameter(description = "应用ID") + private String appId; + + /** + * 授权主体 + */ + @Parameter(description = "授权主体名称") + private String subjectName; + + /** + * 授权主体ID + */ + @Parameter(description = "授权主体ID") + private String subjectId; + + /** + * 主体类型(用户、分组、组织机构) + */ + @Parameter(description = "主体类型") + private PolicySubjectType subjectType; + + /** + * 应用名称 + */ + @Parameter(description = "应用名称") + private String appName; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/query/AppAccountQuery.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/query/AppAccountQuery.java new file mode 100644 index 00000000..064bc9ee --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/query/AppAccountQuery.java @@ -0,0 +1,64 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.app.query; + +import org.springdoc.api.annotations.ParameterObject; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * AppAccountCreateParam 应用账户查询入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/24 22:13 + */ +@Data +@Schema(description = "应用账户查询入参") +@ParameterObject +public class AppAccountQuery { + + /** + * appId + */ + @Parameter(description = "appId") + private String appId; + /** + * 用户ID + */ + @Parameter(description = "用户ID") + private String userId; + /** + * 用户名 + */ + @Parameter(description = "用户名") + private String username; + /** + * 账户名称 + */ + @Parameter(description = "账户名称") + private String account; + + /** + * 应用名称 + */ + @Parameter(description = "应用名称") + private String appName; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/query/AppPolicyQuery.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/query/AppPolicyQuery.java new file mode 100644 index 00000000..4b38146f --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/query/AppPolicyQuery.java @@ -0,0 +1,83 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.app.query; + +import java.io.Serializable; + +import javax.validation.constraints.NotNull; + +import org.springdoc.api.annotations.ParameterObject; + +import cn.topiam.employee.common.enums.PolicyEffect; +import cn.topiam.employee.common.enums.PolicyObjectType; +import cn.topiam.employee.common.enums.PolicySubjectType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 分页查询策略入参 + * + * @author TopIAM + */ +@Data +@Schema(description = "查询权限策略列表入参") +@ParameterObject +public class AppPolicyQuery implements Serializable { + + /** + * 所属应用 + */ + @NotNull(message = "资源所属应用不能为空") + @Parameter(description = "所属应用") + private Long appId; + + /** + * 授权主体Id + */ + @Parameter(description = "授权主体Id") + private String subjectId; + + /** + * 权限主体类型(用户、角色、分组、组织机构) + */ + @NotNull(message = "授权主体类型不能为空") + @Parameter(description = "授权主体类型") + private PolicySubjectType subjectType; + + /** + * 授权客体Id + */ + @Parameter(description = "授权客体Id") + private String objectId; + + /** + * 权限客体类型(权限、角色) + */ + @NotNull(message = "授权客体类型不能为空") + @Parameter(description = "授权客体类型") + private PolicyObjectType objectType; + + /** + * 规则效果 + */ + @Parameter(description = "规则效果") + private PolicyEffect effect; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/standard/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/standard/package-info.java new file mode 100644 index 00000000..da8c9533 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/standard/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.app.standard; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/authentication/IdentityProviderEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/authentication/IdentityProviderEntity.java new file mode 100644 index 00000000..a6e5eddd --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/authentication/IdentityProviderEntity.java @@ -0,0 +1,96 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.authentication; + +import java.io.Serial; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import cn.topiam.employee.common.enums.IdentityProviderCategory; +import cn.topiam.employee.common.enums.IdentityProviderType; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + *

+ * 社交身份认证源配置 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-16 + */ +@Getter +@Setter +@ToString +@Entity +@Accessors(chain = true) +@Table(name = "identity_provider") +public class IdentityProviderEntity extends BaseEntity { + + @Serial + private static final long serialVersionUID = -7936931011805155568L; + + /** + * 名称 + */ + @Column(name = "name_") + private String name; + + /** + * 唯一CODE 不可修改 + */ + @Column(name = "code_") + private String code; + + /** + * 平台 + */ + @Column(name = "type_") + private IdentityProviderType type; + + /** + * 分类 + */ + @Column(name = "category_") + private IdentityProviderCategory category; + + /** + * 配置JSON串 + */ + @Column(name = "config_") + private String config; + + /** + * 是否启用 + */ + @Column(name = "is_enabled") + private Boolean enabled; + + /** + * 是否展示 + */ + @Column(name = "is_displayed") + private Boolean displayed; + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/IdentitySourceEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/IdentitySourceEntity.java new file mode 100644 index 00000000..ff4d9bd5 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/IdentitySourceEntity.java @@ -0,0 +1,108 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.identitysource; + +import java.io.Serial; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import org.hibernate.annotations.Type; + +import cn.topiam.employee.common.entity.identitysource.config.JobConfig; +import cn.topiam.employee.common.entity.identitysource.config.StrategyConfig; +import cn.topiam.employee.common.enums.identityprovider.IdentitySourceProvider; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; +import lombok.extern.slf4j.Slf4j; + +/** + *

+ * 社交身份认证源配置 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-16 + */ +@Getter +@Setter +@ToString +@Entity +@Accessors(chain = true) +@Slf4j +@Table(name = "identity_source") +public class IdentitySourceEntity extends BaseEntity { + + @Serial + private static final long serialVersionUID = -7936931011805155568L; + /** + * 名称 + */ + @Column(name = "name_") + private String name; + + /** + * 唯一CODE 不可修改 + */ + @Column(name = "code_") + private String code; + + /** + * 平台 + */ + @Column(name = "provider_") + private IdentitySourceProvider provider; + + /** + * 基础配置JSON串 + */ + @Column(name = "basic_config") + private String basicConfig; + + /** + * 同步策略JSON串 + */ + @Column(name = "strategy_config") + @Type(type = "json") + private StrategyConfig strategyConfig; + + /** + * 执行计划JSON串 + */ + @Column(name = "job_config") + @Type(type = "json") + private JobConfig jobConfig; + + /** + * 是否启用 + */ + @Column(name = "is_enabled") + private Boolean enabled; + + /** + * 是否已配置 + */ + @Column(name = "is_configured") + private Boolean configured; + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/IdentitySourceEventRecordEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/IdentitySourceEventRecordEntity.java new file mode 100644 index 00000000..3cb96865 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/IdentitySourceEventRecordEntity.java @@ -0,0 +1,99 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.identitysource; + +import java.time.LocalDateTime; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import cn.topiam.employee.common.enums.SyncStatus; +import cn.topiam.employee.common.enums.identitysource.IdentitySourceActionType; +import cn.topiam.employee.common.enums.identitysource.IdentitySourceObjectType; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + * 身份源事件记录 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/22 23:51 + */ +@Getter +@Setter +@ToString +@Entity +@Accessors(chain = true) +@NoArgsConstructor +@Table(name = "identity_source_event_record") +public class IdentitySourceEventRecordEntity extends BaseEntity { + + /** + * 身份源ID + */ + @Column(name = "identity_source_id") + private Long identitySourceId; + + /** + * 动作类型 + */ + @Column(name = "action_type") + private IdentitySourceActionType actionType; + + /** + * 对象ID + */ + @Column(name = "object_id") + private String objectId; + + /** + * 对象名称 + */ + @Column(name = "object_name") + private String objectName; + + /** + * 对象类型 + */ + @Column(name = "object_type") + private IdentitySourceObjectType objectType; + + /** + * 事件时间 + */ + @Column(name = "event_time") + private LocalDateTime eventTime; + + /** + * 事件状态 + */ + @Column(name = "status_") + private SyncStatus status; + + /** + * 描述 + */ + @Column(name = "desc_") + private String desc; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/IdentitySourceSyncHistoryEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/IdentitySourceSyncHistoryEntity.java new file mode 100644 index 00000000..ec61fde5 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/IdentitySourceSyncHistoryEntity.java @@ -0,0 +1,117 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.identitysource; + +import java.time.LocalDateTime; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import cn.topiam.employee.common.enums.SyncStatus; +import cn.topiam.employee.common.enums.TriggerType; +import cn.topiam.employee.common.enums.identitysource.IdentitySourceObjectType; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + * 身份源同步记录表 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/22 23:51 + */ +@Getter +@Setter +@ToString +@Entity +@Accessors(chain = true) +@NoArgsConstructor +@Table(name = "identity_source_sync_history") +public class IdentitySourceSyncHistoryEntity extends BaseEntity { + + /** + * 批号 + */ + @Column(name = "batch_") + private String batch; + + /** + * 身份源ID + */ + @Column(name = "identity_source_id") + private Long identitySourceId; + + /** + * 创建数量 + */ + @Column(name = "created_count") + private Integer createdCount; + + /** + * 更新数量 + */ + @Column(name = "updated_count") + private Integer updatedCount; + + /** + * 删除数量 + */ + @Column(name = "deleted_count") + private Integer deletedCount; + + /** + * 跳过数量 + */ + @Column(name = "skipped_count") + private Integer skippedCount; + + /** + * 开始时间 + */ + @Column(name = "start_time") + private LocalDateTime startTime; + + /** + * 结束时间 + */ + @Column(name = "end_time") + private LocalDateTime endTime; + + /** + * 对象类型(用户、组织) + */ + @Column(name = "object_type") + private IdentitySourceObjectType objectType; + + /** + * 触发类型(手动、任务、事件) + */ + @Column(name = "trigger_type") + private TriggerType triggerType; + + /** + * 触发类型(手动、任务、事件) + */ + @Column(name = "status_") + private SyncStatus status; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/IdentitySourceSyncRecordEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/IdentitySourceSyncRecordEntity.java new file mode 100644 index 00000000..79b95485 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/IdentitySourceSyncRecordEntity.java @@ -0,0 +1,91 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.identitysource; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import cn.topiam.employee.common.enums.SyncStatus; +import cn.topiam.employee.common.enums.identitysource.IdentitySourceActionType; +import cn.topiam.employee.common.enums.identitysource.IdentitySourceObjectType; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + * 身份源同步详情 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/22 23:51 + */ +@Getter +@Setter +@ToString +@Entity +@Accessors(chain = true) +@NoArgsConstructor +@Table(name = "identity_source_sync_record") +public class IdentitySourceSyncRecordEntity extends BaseEntity { + + /** + * 同步历史ID + */ + @Column(name = "sync_history_id") + private Long syncHistoryId; + + /** + * 动作类型 + */ + @Column(name = "action_type") + private IdentitySourceActionType actionType; + + /** + * 对象ID + */ + @Column(name = "object_id") + private String objectId; + + /** + * 对象名称 + */ + @Column(name = "object_name") + private String objectName; + + /** + * 对象类型 + */ + @Column(name = "object_type") + private IdentitySourceObjectType objectType; + + /** + * 状态 + */ + @Column(name = "status_") + private SyncStatus status; + + /** + * 描述 + */ + @Column(name = "desc_") + private String desc; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/config/JobConfig.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/config/JobConfig.java new file mode 100644 index 00000000..40e8daed --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/config/JobConfig.java @@ -0,0 +1,249 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.identitysource.config; + +import java.text.ParseException; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Objects; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import org.springframework.scheduling.support.CronExpression; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.cronutils.builder.CronBuilder; +import com.cronutils.model.Cron; +import com.cronutils.model.CronType; +import com.cronutils.model.definition.CronDefinitionBuilder; +import com.cronutils.model.field.expression.Every; +import com.cronutils.model.field.expression.FieldExpression; +import com.cronutils.model.field.value.IntegerFieldValue; +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnore; + +import cn.topiam.employee.support.exception.TopIamException; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import static com.cronutils.model.field.expression.FieldExpressionFactory.*; + +/** + * 任务配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/9/24 23:09 + */ +@Slf4j +@Data +@Schema(description = "任务配置") +public class JobConfig { + /** + * 模式 + */ + @Parameter(description = "任务执行模式") + @NotNull(message = "请选择任务执行模式") + private Mode mode; + + /** + * 任务执行星期值 + */ + @NotNull(message = "请选择任务执行星期值") + @Parameter(description = "任务执行星期值") + private List dayOfWeek; + + /** + * 值 + */ + @Parameter(description = "任务执行值") + @NotEmpty(message = "任务执行值不能为空") + @JsonAlias({ "time", "interval" }) + private String value; + + public enum Mode { + /** + * 周期 + */ + period, + /** + * 定时 + */ + timed + } + + /** + * 星期几 + */ + public enum DayOfWeek { + /** + * 总是 + */ + always(between(1, 7)), + /** + * 周一 + */ + monday(on(1)), + /** + * 周二 + */ + tuesday(on(2)), + /** + * 周三 + */ + wednesday(on(3)), + /** + * 周四 + */ + thursday(on(4)), + /** + * 周五 + */ + friday(on(5)), + /** + * 周六 + */ + saturday(on(6)), + /** + * 周天 + */ + sunday(on(7)); + + private final FieldExpression code; + + DayOfWeek(FieldExpression code) { + this.code = code; + } + + public FieldExpression getCode() { + return code; + } + } + + /** + * 获取表达式 + * + * @return {@link String} + */ + @JSONField(serialize = false, deserialize = false) + @JsonIgnore + public String getCronExpression(CronType cronType) { + if (!(cronType.equals(CronType.SPRING) || cronType.equals(CronType.QUARTZ))) { + throw new TopIamException("不支持该类型 [" + cronType + "]"); + } + //小时 + FieldExpression hour = always(); + //分钟 + FieldExpression minute = always(); + //秒 + FieldExpression second = always(); + //处理星期 + FieldExpression dayOfWeek = null; + if (this.dayOfWeek.contains(DayOfWeek.always)) { + dayOfWeek = DayOfWeek.always.getCode(); + } else { + for (DayOfWeek week : this.dayOfWeek) { + if (Objects.isNull(dayOfWeek)) { + dayOfWeek = week.getCode(); + continue; + } + dayOfWeek = dayOfWeek.and(week.getCode()); + } + } + //模式为定时 解析时分秒 + if (mode.equals(JobConfig.Mode.timed)) { + LocalTime time = LocalTime.parse(value, DateTimeFormatter.ofPattern("H[H]:mm:ss")); + hour = on(time.getHour()); + minute = on(time.getMinute()); + second = on(time.getSecond()); + } + //模式为周期(0- 某个小时)执行 + if (mode.equals(JobConfig.Mode.period)) { + hour = new Every(on(0), new IntegerFieldValue(Integer.parseInt(value))); + minute = on(0); + second = on(0); + } + /* + * Java(Quartz) + * * * * * * * * + * - - - - - - - + * | | | | | | | + * | | | | | | + year [optional] + * | | | | | +----- day of week (1 - 7) sun,mon,tue,wed,thu,fri,sat + * | | | | +---------- month (1 - 12) OR jan,feb,mar,apr ... + * | | | +--------------- day of month (1 - 31) + * | | +-------------------- hour (0 - 23) + * | +------------------------- min (0 - 59) + * +------------------------------ second (0 - 59) + */ + + /* + * Java(Spring) + * * * * * * * * + * - - - - - - - + * | | | | | | | + * | | | | | | + year [optional] + * | | | | | +----- day of week (1 - 7) sun,mon,tue,wed,thu,fri,sat + * | | | | +---------- month (1 - 12) OR jan,feb,mar,apr ... + * | | | +--------------- day of month (1 - 31) + * | | +-------------------- hour (0 - 23) + * | +------------------------- min (0 - 59) + * +------------------------------ second (0 - 59) + */ + CronBuilder cronBuilder = CronBuilder + .cron(CronDefinitionBuilder.instanceDefinitionFor(cronType)) + //秒 + .withSecond(second) + //分钟 + .withMinute(minute) + //小时 + .withHour(hour) + //天 + .withDoM(questionMark()) + //月份 + .withMonth(always()) + //星期几 + .withDoW(dayOfWeek); + //年 + if (cronType.equals(CronType.QUARTZ)) { + cronBuilder.withYear(always()); + } + Cron cron = cronBuilder.instance(); + // Obtain the string expression + String cronAsString = cron.asString(); + //QUARTZ + if (cronType.equals(CronType.QUARTZ)) { + try { + org.quartz.CronExpression cronExpression = new org.quartz.CronExpression( + cronAsString); + log.debug("Quartz Cron Expression: \n{} ", cronExpression.getExpressionSummary()); + return cronExpression.toString(); + } catch (ParseException exception) { + throw new RuntimeException(exception); + } + } + //SPRING + CronExpression parse = CronExpression.parse(cronAsString); + log.debug("Spring Cron Expression: {} ", parse); + return parse.toString(); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/config/StrategyConfig.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/config/StrategyConfig.java new file mode 100644 index 00000000..19ca62d8 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/identitysource/config/StrategyConfig.java @@ -0,0 +1,87 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.identitysource.config; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 策略配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/9/24 23:10 + */ +@Data +@Schema(description = "策略配置") +public class StrategyConfig { + + /** + * 组织策略 + */ + @Valid + @NotNull(message = "组织策略不能为空") + @Parameter(description = "组织策略") + private Organization organization; + + /** + * 用户策略 + */ + @Valid + @NotNull(message = "用户策略不能为空") + @Parameter(description = "用户策略") + private User user; + + @Data + public static class Organization { + + /** + * 目标ID + */ + @NotEmpty(message = "目标ID不能为空") + @Parameter(description = "目标ID") + private String targetId; + } + + @Data + public static class User { + + /** + * 默认密码 + */ + @Parameter(description = "默认密码") + private String defaultPassword; + + /** + * 是否启用 + */ + @Parameter(description = "是否启用") + private Boolean enabled; + + /** + * 邮件通知 + */ + @Parameter(description = "邮件通知") + private Boolean emailNotify; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/package-info.java new file mode 100644 index 00000000..d6c7356a --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/setting/AdministratorEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/setting/AdministratorEntity.java new file mode 100644 index 00000000..bd286eef --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/setting/AdministratorEntity.java @@ -0,0 +1,118 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.setting; + +import java.io.Serial; +import java.time.LocalDateTime; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import cn.topiam.employee.common.enums.UserStatus; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + *

+ * 管理员表 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-07-31 + */ +@Getter +@Setter +@ToString +@Accessors(chain = true) +@Entity +@Table(name = "`administrator`") +public class AdministratorEntity extends BaseEntity { + + @Serial + private static final long serialVersionUID = -2619231849746900857L; + + /** + * 用户名 + */ + @Column(name = "username_") + private String username; + + /** + * 密码 + */ + @Column(name = "password_") + private String password; + + /** + * 邮箱 + */ + @Column(name = "email_") + private String email; + + /** + * 手机号 + */ + @Column(name = "phone_") + private String phone; + + /** + * 头像URL + */ + @Column(name = "avatar_") + private String avatar; + + /** + * 状态 ENABLE:启用 DISABLE:禁用 LOCKING:锁定 + */ + @Column(name = "status_") + private UserStatus status; + + /** + * 邮箱验证有效 + */ + @Column(name = "email_verified") + private Boolean emailVerified; + + /** + * 认证次数 + */ + @Column(name = "auth_total") + private Long authTotal; + /** + * 上次认证IP + */ + @Column(name = "last_auth_ip") + private String lastAuthIp; + + /** + * 上次认证时间 + */ + @Column(name = "last_auth_time") + private LocalDateTime lastAuthTime; + + /** + * 扩展参数 + */ + @Column(name = "expand_") + private String expand; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/setting/MailTemplateEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/setting/MailTemplateEntity.java new file mode 100644 index 00000000..b8dead60 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/setting/MailTemplateEntity.java @@ -0,0 +1,78 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.setting; + +import java.io.Serial; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import cn.topiam.employee.common.enums.MailType; +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + *

+ * 邮件模板 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-13 + */ +@Getter +@Setter +@ToString +@Entity +@Accessors(chain = true) +@Table(name = "mail_template") +public class MailTemplateEntity extends BaseEntity { + + @Serial + private static final long serialVersionUID = 5983857137670090984L; + /** + * 模板类型 + */ + @Column(name = "type_") + private MailType type; + + /** + * 发送人 + *

+ * 你可以包括以下宏命令:${client_name},${time},${user_email},${client_description},${verify_code}。 例如:${client_name} + */ + @Column(name = "sender_") + private String sender; + + /** + * 主题 + * 你可以包括以下宏:${client_name},${time},${client_description},${user_email}。 例如:你正在修改绑定邮箱,你的验证码为:${verify_code}! + */ + @Column(name = "subject_") + private String subject; + + /** + * 内容 + */ + @Column(name = "content_") + private String content; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/setting/SettingEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/setting/SettingEntity.java new file mode 100644 index 00000000..96cb3905 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/setting/SettingEntity.java @@ -0,0 +1,63 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.setting; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + *

+ * 设置表 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-10-01 + */ +@Getter +@Setter +@ToString +@Entity +@Accessors(chain = true) +@Table(name = "`setting`") +public class SettingEntity extends BaseEntity { + + /** + * name + */ + @Column(name = "name_") + private String name; + /** + * value + */ + @Column(name = "value_") + private String value; + /** + * desc + */ + @Column(name = "desc_") + private String desc; + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/setting/config/SmsConfig.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/setting/config/SmsConfig.java new file mode 100644 index 00000000..156ba6b5 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/setting/config/SmsConfig.java @@ -0,0 +1,81 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.entity.setting.config; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +import cn.topiam.employee.common.enums.Language; +import cn.topiam.employee.common.enums.SmsType; +import cn.topiam.employee.common.message.enums.SmsProvider; +import cn.topiam.employee.common.message.sms.SmsProviderConfig; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; + +/** + * 短信配置 + * + * @author TopIAM + */ +@Data +public class SmsConfig implements Serializable { + + @Serial + private static final long serialVersionUID = 5293005308937620292L; + + /** + * 提供商 + */ + private SmsProvider provider; + + /** + * 语言 + */ + private Language language; + + /** + * 配置 + */ + private SmsProviderConfig config; + + /** + * 模版配置 + */ + private List templates; + + public SmsConfig() { + } + + @Data + public static class TemplateConfig implements Serializable { + + @Serial + private static final long serialVersionUID = 2801844583775238689L; + + @Parameter(description = "短信类型") + private SmsType type; + @Parameter(description = "模板ID/CODE") + private String code; + + public TemplateConfig() { + } + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/AuthenticationType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/AuthenticationType.java new file mode 100644 index 00000000..e7b5bfb4 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/AuthenticationType.java @@ -0,0 +1,86 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 认证方式 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/31 22:45 + */ +public enum AuthenticationType implements BaseEnum { + /** + * from表单 + */ + FORM("form", "FORM表单"), + /** + * 短信验证码 + */ + SMS("sms", "短信验证码"), + /** + * 社交认证 + */ + SOCIAL("social", "社交认证"); + + @JsonValue + private String code; + + private String desc; + + AuthenticationType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + /** + * 获取认证平台 + * + * @param code {@link String} + * @return {@link AuthenticationType} + */ + @EnumConvert + public static AuthenticationType getType(String code) { + AuthenticationType[] values = values(); + for (AuthenticationType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + throw new NullPointerException("未获取到对应平台"); + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/BaseEnum.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/BaseEnum.java new file mode 100644 index 00000000..6936b003 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/BaseEnum.java @@ -0,0 +1,40 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +/** + * + * @author TopIAM + * Created by support@topiam.cn on 2022/11/27 23:46 + */ +public interface BaseEnum { + /** + * 获取code + * + * @return {@link String} + */ + String getCode(); + + /** + * 获取desc + * + * @return {@link String} + */ + String getDesc(); + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/CaptchaProviderType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/CaptchaProviderType.java new file mode 100644 index 00000000..cfc350ea --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/CaptchaProviderType.java @@ -0,0 +1,87 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 验证码提供类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/7 20:01 + */ +public enum CaptchaProviderType implements BaseEnum { + + /** + * ALIYUN + */ + ALIYUN("aliyun", "阿里云"), + /** + *TENCENT + */ + TENCENT("tencent", "腾讯"), + /** + *GEE_TEST + */ + GEE_TEST("geetest", "极验"), + /** + *HCAPTCHA + */ + HCAPTCHA("hcaptcha", "Hcaptcha"), + /** + *RECAPTCHA + */ + RECAPTCHA("recaptcha", "reCAPTCHA"); + + @JsonValue + private final String code; + private final String desc; + + /** + * 获取提供商 + * + * @param code {@link String} + * @return {@link CaptchaProviderType} + */ + @EnumConvert + public static CaptchaProviderType getType(String code) { + CaptchaProviderType[] values = values(); + for (CaptchaProviderType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + throw new NullPointerException("未获取到对应提供商"); + } + + CaptchaProviderType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + @Override + public String getDesc() { + return null; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/CheckValidityType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/CheckValidityType.java new file mode 100644 index 00000000..9c7835cb --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/CheckValidityType.java @@ -0,0 +1,47 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +/** + * 检查有效性类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/17 19:22 + */ +public enum CheckValidityType { + /** + * 邮箱 + */ + EMAIL, + /** + * 手机号 + */ + PHONE, + /** + * 名称 + */ + NAME, + /** + * 编码 + */ + CODE, + /** + * 用户名 + */ + USERNAME; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/DataOrigin.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/DataOrigin.java new file mode 100644 index 00000000..33491834 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/DataOrigin.java @@ -0,0 +1,106 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 数据来源 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/7 20:35 + */ +public enum DataOrigin implements BaseEnum { + + /** + * 系统添加 + */ + INPUT("input", "自建"), + /** + * 钉钉 + */ + DING_TALK("dingtalk", "钉钉导入"), + + /** + * 企业微信 + */ + WECHAT_WORK("wechat", "企业微信导入"), + /** + * 飞书 + */ + FEI_SHU("feishu", "飞书导入"); + + /** + * code + */ + @JsonValue + @JSONField + private final String code; + /** + * desc + */ + private final String desc; + + DataOrigin(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + /** + * 获取来源 + * + * @param code {@link String} + * @return {@link OrganizationType} + */ + @EnumConvert + public static DataOrigin getType(String code) { + DataOrigin[] values = values(); + for (DataOrigin source : values) { + if (String.valueOf(source.getCode()).equals(code)) { + return source; + } + } + return null; + } + + public static DataOrigin getName(String name) { + DataOrigin[] values = values(); + for (DataOrigin source : values) { + if (source.name().equals(name)) { + return source; + } + } + return null; + } + + @Override + public String toString() { + return this.code; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/EnumDeserializer.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/EnumDeserializer.java new file mode 100644 index 00000000..ecdace2f --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/EnumDeserializer.java @@ -0,0 +1,72 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; + +/** + * EnumDeserializer + * + * @author TopIAM + * Created by support@topiam.cn on 2022/11/27 23:48 + */ +public class EnumDeserializer extends JsonDeserializer implements ContextualDeserializer { + /** + * 记录枚举字段的类,用于获取其定义的所有枚举值 + */ + private Class propertyClass; + + public EnumDeserializer() { + } + + public EnumDeserializer(Class propertyClass) { + this.propertyClass = propertyClass; + } + + @Override + public BaseEnum deserialize(JsonParser jsonParser, + DeserializationContext deserializationContext) throws IOException { + String value = jsonParser.getValueAsString(); + if (StringUtils.isBlank(value)) { + return null; + } + // 调用Class的这个方法,获取枚举类的所有枚举值 + return Arrays.stream(propertyClass.getEnumConstants()) + .filter(e -> Objects.equals(e.getCode(), value)).findAny() + .orElseThrow(() -> new IllegalArgumentException( + "No such code of " + propertyClass.getSimpleName())); + } + + @Override + public JsonDeserializer createContextual(DeserializationContext ctxt, + BeanProperty property) { + // 获取枚举字段的类型Class + return new ListEnumDeserializer( + (Class) property.getType().getRawClass()); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/IdentityProviderCategory.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/IdentityProviderCategory.java new file mode 100644 index 00000000..34f25536 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/IdentityProviderCategory.java @@ -0,0 +1,93 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import java.util.List; + +import com.google.common.collect.Lists; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 身份源类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/3/21 23:07 + */ +public enum IdentityProviderCategory implements BaseEnum { + /** + * 社交 + */ + social("social", "社交", Lists.newArrayList( + IdentityProviderType.QQ, + IdentityProviderType.WECHAT_SCAN_CODE, + IdentityProviderType.WEIBO, + IdentityProviderType.GITHUB, + IdentityProviderType.GOOGLE, + IdentityProviderType.ALIPAY)), + /** + * 企业 + */ + enterprise("enterprise", "企业", Lists + .newArrayList( + IdentityProviderType.WECHATWORK_SCAN_CODE, + IdentityProviderType.DINGTALK_SCAN_CODE, + IdentityProviderType.DINGTALK_OAUTH, + IdentityProviderType.LDAP)); + + private final String code; + + private final String desc; + + private final List providers; + + IdentityProviderCategory(String code, String desc, List providers) { + this.code = code; + this.desc = desc; + this.providers = providers; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + public List getProviders() { + return providers; + } + + /** + * 获取类型 + * + * @param code {@link String} + * @return {@link AuthenticationType} + */ + @EnumConvert + public static IdentityProviderCategory getType(String code) { + IdentityProviderCategory[] values = values(); + for (IdentityProviderCategory status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + throw new NullPointerException("未获取到对应平台"); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/IdentityProviderType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/IdentityProviderType.java new file mode 100644 index 00000000..5699dc9c --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/IdentityProviderType.java @@ -0,0 +1,133 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; +import static cn.topiam.employee.common.constants.AuthorizeConstants.AUTHORIZATION_REQUEST_URI; +import static cn.topiam.employee.common.constants.AuthorizeConstants.LOGIN_PATH; + +/** + * 认证提供商 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/13 22:18 + */ +public enum IdentityProviderType implements BaseEnum { + /** + * 微信扫码登录 + */ + WECHAT_SCAN_CODE("wechat_scan_code", "微信扫码登录", + "通过微信扫码进行身份认证"), + /** + * 钉钉扫码登录 + */ + DINGTALK_SCAN_CODE("dingtalk_scan_code", + "钉钉扫码认证", + + "通过钉钉扫码进行身份认证"), + /** + * 钉钉Oauth2 + */ + DINGTALK_OAUTH("dingtalk_oauth", "钉钉Oauth认证", + "通过钉钉进行身份认证"), + /** + * 企业微信 + */ + WECHATWORK_SCAN_CODE("wechatwork_scan_code", + "企业微信扫码认证", + + "通过企业微信同步的用户可使用企业微信扫码登录进行身份认证"), + /** + * QQ + */ + QQ("qq_oauth", "QQ认证", "通过QQ进行身份认证"), + /** + * 微博 + */ + WEIBO("weibo_oauth", "微博认证", "通过微博进行身份认证"), + /** + * Github + */ + GITHUB("github_oauth", "Github", + "通过 GitHub 进行身份认证"), + /** + * Google + */ + GOOGLE("google_oauth", "Google", + "通过 Google 进行身份认证"), + /** + * 支付宝扫码认证 + */ + ALIPAY("alipay_oauth", "支付宝认证", + "通过支付宝进行身份认证"), + + /** + * LDAP + */ + LDAP("ldap", "LDAP 认证源", "通过 LDAP 认证源进行身份验证"); + + @JsonValue + private final String code; + private final String name; + private final String desc; + + IdentityProviderType(String code, String name, String desc) { + this.code = code; + this.name = name; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getName() { + return name; + } + + public String getDesc() { + return desc; + } + + public String getAuthorizationPathPrefix() { + return AUTHORIZATION_REQUEST_URI + "/" + getCode(); + } + + public String getLoginPathPrefix() { + return LOGIN_PATH + "/" + getCode(); + } + + /** + * 获取认证平台 + * + * @param code {@link String} + * @return {@link IdentityProviderType} + */ + @EnumConvert + public static IdentityProviderType getType(String code) { + IdentityProviderType[] values = values(); + for (IdentityProviderType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/Language.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/Language.java new file mode 100644 index 00000000..4f1ae7b0 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/Language.java @@ -0,0 +1,90 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 语言 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/9 21:04 + */ +public enum Language implements BaseEnum { + /** + * 英语 + */ + EN("en", "英语"), + /** + * 中文 + */ + ZH("zh", "中文"); + + /** + * code + */ + @JsonValue + private final String locale; + /** + * desc + */ + private final String desc; + + Language(String locale, String desc) { + this.locale = locale; + this.desc = desc; + } + + public String getLocale() { + return locale; + } + + @Override + public String getCode() { + return locale; + } + + public String getDesc() { + return desc; + } + + /** + * 获取类型 + * + * @param code {@link String} + * @return {@link OrganizationType} + */ + @EnumConvert + public static Language getType(String code) { + Language[] values = values(); + for (Language status : values) { + if (String.valueOf(status.getLocale()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return this.getLocale(); + } + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/ListEnumDeserializer.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/ListEnumDeserializer.java new file mode 100644 index 00000000..940ba366 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/ListEnumDeserializer.java @@ -0,0 +1,91 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.*; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import com.fasterxml.jackson.databind.node.ArrayNode; + +/** + * ListEnumDeserializer + * + * @author TopIAM + * Created by support@topiam.cn on 2022/11/27 22:00 + */ +public class ListEnumDeserializer extends JsonDeserializer> + implements ContextualDeserializer { + /** + * 记录枚举字段的类,用于获取其定义的所有枚举值 + */ + private Class propertyClass; + + public ListEnumDeserializer() { + } + + public ListEnumDeserializer(Class propertyClass) { + this.propertyClass = propertyClass; + } + + @Override + public List deserialize(JsonParser jp, + DeserializationContext cxt) throws IOException { + ArrayNode treeNode = jp.readValueAsTree(); + Field field; + try { + field = jp.getCurrentValue().getClass().getDeclaredField(jp.currentName()); + } catch (NoSuchFieldException e) { + return null; + } + field.setAccessible(true); + if (!field.getType().equals(List.class)) { + return null; + } + List result = new ArrayList<>(); + Iterator elements = treeNode.elements(); + while (elements.hasNext()) { + String value = elements.next().asText(); + try { + //设置值 + BaseEnum anEnum = Arrays.stream(propertyClass.getEnumConstants()) + .filter(e -> Objects.equals(e.getCode(), value)).findAny() + .orElseThrow(() -> new IllegalArgumentException( + "No such code of " + propertyClass.getSimpleName())); + result.add(anEnum); + } catch (IllegalArgumentException ignored) { + } + } + if (result.isEmpty()) { + return null; + } else { + return result; + } + } + + @Override + public JsonDeserializer createContextual(DeserializationContext ctxt, + BeanProperty property) { + // 获取枚举字段的类型Class + return new ListEnumDeserializer( + (Class) property.getType().getContentType().getRawClass()); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/MailType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/MailType.java new file mode 100644 index 00000000..4d818b17 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/MailType.java @@ -0,0 +1,197 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 邮件模板类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/13 23:02 + */ +public enum MailType implements BaseEnum { + + /** + * 绑定邮箱 + */ + BIND_EMAIL("bind_email", "绑定邮箱", "每当用户绑定邮箱时,都会发送此电子邮件。", + "您正在绑定邮箱,验证码为:${verify_code}", "系统账户", + getMailContentPath() + "bind-mail-content.html"), + /** + * 修改绑定邮件 + */ + UPDATE_BIND_MAIL("update_bind_mail", "修改绑定邮件", + "每当用户需要修改绑定邮箱、发送验证码时,都会发送此电子邮件。", + "您正在修改绑定邮箱,验证码为:${verify_code}", "系统账户", + getMailContentPath() + "update-bind-mail-content.html"), + + /** + * 忘记密码 暂未实现 + */ + FORGET_PASSWORD("forget_password", "忘记密码", + "每当用户忘记密码时,都会发送此电子邮件。", + "您正在修改密码,验证码为:${verify_code}", "系统账户", + getMailContentPath() + "update-password-content.html"), + /** + * 修改密码 + */ + UPDATE_PASSWORD("update_password", "修改密码", + "每当用户要求更改密码时,都会发送此电子邮件。", + "您正在修改密码,验证码为:${verify_code}", "系统账户", + getMailContentPath() + "update-password-content.html"), + /** + * 重置密码 + */ + RESET_PASSWORD("reset_password", "重置密码", + "每当用户要求更改密码时,都会发送此电子邮件。", + "您正在重置密码,验证码为:${verify_code}", "系统账户", + getMailContentPath() + "reset-password-content.html"), + /** + * 重置密码成功 TODO 提示信息调整 + */ + RESET_PASSWORD_CONFIRM("reset_password_confirm", "重置密码成功", + "每当用户重置密码成功时,都会发送此电子邮件。", + "您已成功重置密码,如非本人操作请立即修改账户密码", "系统账户", + getMailContentPath() + "reset-password-confirm-content.html"), + + /** + * 验证邮箱 + */ + VERIFY_EMAIL("verify_email", "验证邮箱", + "用户用邮箱初次注册时会发送一封验证邮箱的链接,用户点击邮件内的网址即可完成验证。", + "请点击此链接进行验证: ${verify_link}", "系统账户", + getMailContentPath() + "verify-email-content.html"), + /** + * 二次验证 + */ + AGAIN_VERIFY("again_verify", "二次验证", + "管理员开启邮件二次认证后,用户使用账号密码登录,选择邮件二次认证时。", + "您正在进行二次认证,验证码为:${verify_code},${expire_time}分钟内有效。", + "系统账户", getMailContentPath() + + "again-verify-mail-content.html"), + /** + * 欢迎邮件 + */ + WELCOME_MAIL("welcome_mail", "欢迎邮件", + "一旦用户验证其电子邮件地址,将发送此电子邮件。 如果验证电子邮件已关闭, 则会在用户第一次注册或登录时发送。", + "你已注册成功", + + "系统账户", getMailContentPath() + + "welcome-mail-content.html"), + + /** + * 密码过期提醒 + */ + PASSWORD_SOON_EXPIRED_REMIND("password_soon_expired_remind", + "密码即将到期提醒", + "用户距离上次修改密码有效期即将过期时,都会发送此电子邮件。", + "您的密码将于${expire_days}天后过期, 请尽快修改密码。", + "系统账户", getMailContentPath() + + "password-soon-expired-content.html"), + /** + * 风险预警 暂不实现 + */ + WARNING("warning", "风险预警", "用户每次执行风险操作,都会发送此电子邮件。", + "您的账号存在风险操作, 验证码为:${verify_code}。", "系统账户", + getMailContentPath() + "warning-content.html"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * 名称 + */ + private final String name; + /** + * desc + */ + private final String desc; + /** + * 主题 + */ + private final String subject; + /** + * 发件人 + */ + private final String sender; + /** + * 内容 + */ + private final String content; + + MailType(String code, String name, String desc, String subject, String sender, String content) { + this.code = code; + this.name = name; + this.desc = desc; + this.subject = subject; + this.sender = sender; + this.content = content; + } + + @Override + public String getCode() { + return code; + } + + @Override + public String getDesc() { + return desc; + } + + public String getContent() { + return content; + } + + public String getSubject() { + return subject; + } + + public String getName() { + return name; + } + + public String getSender() { + return sender; + } + + static String getMailContentPath() { + return "mail/content/"; + } + + @EnumConvert + public static MailType getType(String code) { + MailType[] values = values(); + for (MailType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + throw new NullPointerException("未找到该类型"); + } + + @Override + public String toString() { + return this.code; + } + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/MessageCategory.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/MessageCategory.java new file mode 100644 index 00000000..b5270e55 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/MessageCategory.java @@ -0,0 +1,85 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 消息分类 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/9 21:04 + */ +public enum MessageCategory implements BaseEnum { + /** + * 验证码 + */ + CODE("code", "验证码"), + /** + * 通知 + */ + NOTICE("notice", "通知"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + + MessageCategory(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + /** + * 获取类型 + * + * @param code {@link String} + * @return {@link OrganizationType} + */ + @EnumConvert + public static MessageCategory getType(String code) { + MessageCategory[] values = values(); + for (MessageCategory status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return this.code; + } + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/MessageNoticeChannel.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/MessageNoticeChannel.java new file mode 100644 index 00000000..af1740d6 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/MessageNoticeChannel.java @@ -0,0 +1,87 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 消息类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/25 19:19 + */ +public enum MessageNoticeChannel implements BaseEnum { + /** + * 邮件 + */ + MAIL("mail", "邮件"), + /** + * 短信 + */ + SMS("sms", "短信"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + + MessageNoticeChannel(String code, String desc) { + this.code = code; + this.desc = desc; + } + + @Override + public String getCode() { + return code; + } + + @Override + public String getDesc() { + return desc; + } + + /** + * 获取类型 + * + * @param code {@link String} + * @return {@link OrganizationType} + */ + @EnumConvert + public static MessageNoticeChannel getType(String code) { + MessageNoticeChannel[] values = values(); + for (MessageNoticeChannel status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return this.code; + } + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/MfaFactor.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/MfaFactor.java new file mode 100644 index 00000000..50920b17 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/MfaFactor.java @@ -0,0 +1,89 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * MFA方式 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/7/19 23:12 + */ +public enum MfaFactor implements BaseEnum { + /** + * SMS + */ + SMS_OTP("sms_otp", "短信验证码"), + /** + * 邮件验证码 + */ + EMAIL_OTP("email_otp", "邮件验证码"), + + /** + * APP TOTP + */ + APP_TOTP("app_totp", "APP TOTP"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + + MfaFactor(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + /** + * 获取类型 + * + * @param code {@link String} + * @return {@link OrganizationType} + */ + @EnumConvert + public static MfaFactor getType(String code) { + MfaFactor[] values = values(); + for (MfaFactor status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return this.code; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/MfaMode.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/MfaMode.java new file mode 100644 index 00000000..198ce2ab --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/MfaMode.java @@ -0,0 +1,88 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * MFA 模式 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/7/19 23:04 + */ +public enum MfaMode implements BaseEnum { + /** + * 常开模式 + */ + ALWAYS("always", "常开模式"), + /** + * 关闭 + */ + NONE("none", "关闭"), + /** + * 智能模式 + */ + SMART("smart", "智能模式"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + + MfaMode(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + /** + * 获取类型 + * + * @param code {@link String} + * @return {@link OrganizationType} + */ + @EnumConvert + public static MfaMode getType(String code) { + MfaMode[] values = values(); + for (MfaMode status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return this.code; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/OrganizationType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/OrganizationType.java new file mode 100644 index 00000000..809f2451 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/OrganizationType.java @@ -0,0 +1,92 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 组织机构类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/9 21:04 + */ +public enum OrganizationType implements BaseEnum { + /** + * 集团 + */ + GROUP("group", "集团"), + /** + * 公司 + */ + COMPANY("company", "公司"), + /** + * 部门 + */ + DEPARTMENT("department", "部门"), + /** + * 单位 + */ + UNIT("unit", "单位"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + + OrganizationType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + /** + * 获取类型 + * + * @param code {@link String} + * @return {@link OrganizationType} + */ + @EnumConvert + public static OrganizationType getType(String code) { + OrganizationType[] values = values(); + for (OrganizationType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return this.code; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/PasswordStrength.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/PasswordStrength.java new file mode 100644 index 00000000..3046bcdb --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/PasswordStrength.java @@ -0,0 +1,39 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +/** + * 密码强度 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/4 22:45 + */ +public enum PasswordStrength { + /** + * 低 + */ + LOW, + /** + * 中 + */ + MEDIUM, + /** + * 高 + */ + HIGHER +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/PermissionActionType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/PermissionActionType.java new file mode 100644 index 00000000..20de77b5 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/PermissionActionType.java @@ -0,0 +1,82 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * 权限类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/10 22:51 + */ +public enum PermissionActionType implements BaseEnum { + /** + * 菜单 + */ + MENU("MENU", "菜单"), + /** + * 按钮 + */ + BUTTON("BUTTON", "按钮"), + /** + * API + */ + API("API", "API"), + /** + * 数据 + */ + DATA("DATA", "数据"), + /** + * 其他 + */ + OTHER("OTHER", "其他"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + + PermissionActionType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + public static PermissionActionType getType(String code) { + PermissionActionType[] values = values(); + for (PermissionActionType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/PolicyEffect.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/PolicyEffect.java new file mode 100644 index 00000000..9ef756ce --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/PolicyEffect.java @@ -0,0 +1,79 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 规则效果 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/4 19:07 + */ +public enum PolicyEffect implements BaseEnum { + /** + * 允许 + */ + Allow("ALLOW", "允许"), + /** + * 拒绝 + */ + Deny("DENY", "拒绝"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + + PolicyEffect(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + /** + * 获取类型 + * + * @param code {@link String} + * @return {@link PolicyEffect} + */ + @EnumConvert + public static PolicyEffect getType(String code) { + PolicyEffect[] values = values(); + for (PolicyEffect status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/PolicyObjectType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/PolicyObjectType.java new file mode 100644 index 00000000..33fe671a --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/PolicyObjectType.java @@ -0,0 +1,83 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 权限策略客体类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/4 19:05 + */ +public enum PolicyObjectType implements BaseEnum { + /** + * 角色 + */ + ROLE("ROLE", "角色"), + /** + * 权限 + */ + PERMISSION("PERMISSION", "权限"), + /** + * 资源 + */ + RESOURCE("RESOURCE", "资源"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + + PolicyObjectType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + /** + * 获取类型 + * + * @param code {@link String} + * @return {@link PolicyObjectType} + */ + @EnumConvert + public static PolicyObjectType getType(String code) { + PolicyObjectType[] values = values(); + for (PolicyObjectType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/PolicySubjectType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/PolicySubjectType.java new file mode 100644 index 00000000..571cc080 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/PolicySubjectType.java @@ -0,0 +1,92 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 权限策略主体类型 + * 用户、角色、分组、组织机构 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/4 19:05 + */ +public enum PolicySubjectType implements BaseEnum { + /** + * 角色 + */ + ROLE("ROLE", "角色"), + /** + * 用户 + */ + USER("USER", "用户"), + /** + * 组织机构 + */ + ORGANIZATION("ORGANIZATION", "组织机构"), + /** + * 分组 + */ + USER_GROUP("USER_GROUP", "分组"), + /** + * TODO 客户端 + */ + CLIENT("CLIENT", "客户端"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + + PolicySubjectType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + /** + * 获取类型 + * + * @param code {@link String} + * @return {@link PolicySubjectType} + */ + @EnumConvert + public static PolicySubjectType getType(String code) { + PolicySubjectType[] values = values(); + for (PolicySubjectType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/SecretType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/SecretType.java new file mode 100644 index 00000000..3f6822c6 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/SecretType.java @@ -0,0 +1,84 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +import lombok.Getter; +import static cn.topiam.employee.support.constant.EiamConstants.TOPIAM_ENCRYPT_SECRET; +import static cn.topiam.employee.support.constant.EiamConstants.TOPIAM_LOGIN_SECRET; + +/** + * 秘钥类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/9 23:15 + */ +public enum SecretType implements BaseEnum { + /** + * 登录 + */ + LOGIN("login", TOPIAM_LOGIN_SECRET, "登录"), + /** + * 加密 + */ + ENCRYPT("encrypt", TOPIAM_ENCRYPT_SECRET, "加密"); + + @Getter + @JsonValue + private final String code; + @Getter + private final String key; + + private final String desc; + + SecretType(String code, String key, String desc) { + this.code = code; + this.key = key; + this.desc = desc; + } + + /** + * 获取来源 + * + * @param code {@link String} + * @return {@link OrganizationType} + */ + @EnumConvert + public static SecretType getType(String code) { + SecretType[] values = values(); + for (SecretType source : values) { + if (String.valueOf(source.getCode()).equals(code)) { + return source; + } + } + return null; + } + + @Override + public String toString() { + return this.code; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/SmsType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/SmsType.java new file mode 100644 index 00000000..2064bf43 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/SmsType.java @@ -0,0 +1,147 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 短信类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/25 19:19 + */ +public enum SmsType implements BaseEnum { + /** + * 绑定手机号 + */ + BIND_PHONE("bind_phone", "绑定手机号", MessageCategory.CODE), + /** + * 绑定,修改手机号成功 + */ + BIND_PHONE_SUCCESS("bind_phone_success", "绑定手机号成功", + MessageCategory.CODE), + /** + * 修改绑定手机号 + */ + UPDATE_PHONE("update_phone", "修改手机号", + MessageCategory.CODE), + /** + * 忘记密码 暂未实现 + */ + FORGET_PASSWORD("forget_password", "忘记密码", + MessageCategory.CODE), + /** + * 修改密码 + */ + UPDATE_PASSWORD("update_password", "修改密码", + MessageCategory.CODE), + /** + * 重置密码 + */ + RESET_PASSWORD("reset_password", "重置密码", + MessageCategory.NOTICE), + /** + * 重置密码成功 + */ + RESET_PASSWORD_SUCCESS("reset_password_success", "重置密码成功", + MessageCategory.NOTICE), + /** + * 登录验证 未使用 + */ + LOGIN("login", "登录验证", MessageCategory.CODE), + + /** + * 二次验证 + */ + AGAIN_VERIFY("again_verify", "二次验证", MessageCategory.CODE), + + /** + * 欢迎短信 + */ + WELCOME_SMS("welcome_sms", "欢迎短信", MessageCategory.NOTICE), + + /** + * 密码过期提醒 + */ + PASSWORD_SOON_EXPIRED_REMIND("password_soon_expired_remind", + "密码过期提醒", + MessageCategory.NOTICE), + + /** + * 风险预警 暂不实现 + */ + WARING("warning", "风险预警", MessageCategory.NOTICE); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + + /** + * 短信类型 + */ + private final MessageCategory category; + + SmsType(String code, String desc, MessageCategory category) { + this.code = code; + this.desc = desc; + this.category = category; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + public MessageCategory getCategory() { + return category; + } + + /** + * 获取类型 + * + * @param code {@link String} + * @return {@link OrganizationType} + */ + @EnumConvert + public static SmsType getType(String code) { + SmsType[] values = values(); + for (SmsType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return this.code; + } + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/SyncStatus.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/SyncStatus.java new file mode 100644 index 00000000..1c38b587 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/SyncStatus.java @@ -0,0 +1,85 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 同步状态 + * + * @author TopIAM + */ +public enum SyncStatus implements BaseEnum { + /** + * 成功 + */ + SUCCESS("success", "成功"), + /** + * 失败 + */ + FAIL("fail", "失败"), + /** + * 跳过 + */ + SKIP("skip", "跳过"), + /** + * 同步中 + */ + PENDING("pending", "同步中"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + + SyncStatus(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + @EnumConvert + public static SyncStatus getStatus(String code) { + SyncStatus[] values = values(); + for (SyncStatus status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return code; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/TriggerType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/TriggerType.java new file mode 100644 index 00000000..df775aff --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/TriggerType.java @@ -0,0 +1,73 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 触发类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/3/9 23:47 + */ +public enum TriggerType implements BaseEnum { + /** + * 手动触发 + */ + MANUAL("manual", "手动触发"), + /** + * 任务触发 + */ + JOB("job", "任务触发"); + + @JsonValue + private final String code; + private final String desc; + + TriggerType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + /** + * 获取类型 + * + * @param code {@link String} + * @return {@link TriggerType} + */ + @EnumConvert + public static TriggerType getType(String code) { + TriggerType[] values = values(); + for (TriggerType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/UserGender.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/UserGender.java new file mode 100644 index 00000000..00460e36 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/UserGender.java @@ -0,0 +1,77 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 用户性别 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/7/17 19:28 + */ +public enum UserGender implements BaseEnum { + /** + * 男 + */ + MALE("1", "男"), + /** + * 女 + */ + FEMALE("0", "女"), + /** + * 未知 + */ + UNKNOWN("-1", "未知"); + + @JsonValue + private final String code; + private final String desc; + + UserGender(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + /** + * 获取认证平台 + * + * @param code {@link String} + * @return {@link UserGender} + */ + @EnumConvert + public static UserGender getType(String code) { + UserGender[] values = values(); + for (UserGender status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + throw new NullPointerException("未获取到对应性别"); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/UserIdType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/UserIdType.java new file mode 100644 index 00000000..61d33234 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/UserIdType.java @@ -0,0 +1,91 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * + * @author TopIAM + * Created by support@topiam.cn on 2022/10/6 22:11 + */ +public enum UserIdType implements BaseEnum { + /** + * 身份证号 + */ + IDENTITY_CARD("identity_card", "身份证号"); + + /** + * code + */ + @JsonValue + @JSONField + private final String code; + /** + * desc + */ + private final String desc; + + UserIdType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + /** + * 获取来源 + * + * @param code {@link String} + * @return {@link OrganizationType} + */ + @EnumConvert + public static UserIdType getType(String code) { + UserIdType[] values = values(); + for (UserIdType source : values) { + if (String.valueOf(source.getCode()).equals(code)) { + return source; + } + } + return null; + } + + public static UserIdType getName(String name) { + UserIdType[] values = values(); + for (UserIdType source : values) { + if (source.name().equals(name)) { + return source; + } + } + return null; + } + + @Override + public String toString() { + return this.code; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/UserStatus.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/UserStatus.java new file mode 100644 index 00000000..33115891 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/UserStatus.java @@ -0,0 +1,92 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 用户状态 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/31 22:50 + */ +public enum UserStatus implements BaseEnum { + /** + * 已启用 + */ + ENABLE("enabled", "启用"), + /** + * 已禁用 + */ + DISABLE("disabled", "禁用"), + + /** + * 已锁定 + */ + LOCKED("locked", "锁定"), + /** + * 过期锁定 + */ + EXPIRED_LOCKED("expired_locked", "过期锁定"), + /** + * 密码过期锁定 + */ + PASS_WORD_EXPIRED_LOCKED("password_expired_locked", + "密码过期锁定"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + + UserStatus(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + @EnumConvert + public static UserStatus getStatus(String code) { + UserStatus[] values = values(); + for (UserStatus status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return code; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/UserType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/UserType.java new file mode 100644 index 00000000..781cf293 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/UserType.java @@ -0,0 +1,90 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 用户类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/22 23:35 + */ +public enum UserType implements BaseEnum { + /** + * 用户 + */ + USER("user", "用户"), + /** + * 管理员 + */ + ADMIN("admin", "管理员"), + /** + * 开发人员 + */ + DEVELOPER("developer", "开发人员"), + /** + * 未知 + */ + UNKNOWN("unknown", "未知"); + + @JsonValue + private String code; + + private String desc; + + UserType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + /** + * 获取审计类型 + * + * @param code {@link String} + * @return {@link UserType} + */ + @EnumConvert + public static UserType getType(String code) { + UserType[] values = values(); + for (UserType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + throw new NullPointerException("未获取到类型"); + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AppCertUsingType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AppCertUsingType.java new file mode 100644 index 00000000..02c6ef91 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AppCertUsingType.java @@ -0,0 +1,76 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.common.enums.BaseEnum; +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 证书使用类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/31 22:28 + */ +public enum AppCertUsingType implements BaseEnum { + /** + * OIDC JWK + */ + OIDC_JWK("oidc_jwk", "OIDC JWK"), + /** + * SAML签名 + */ + SAML_SIGN("saml_sign", "SAML 签名"), + /** + * SAML 加密 + */ + SAML_ENCRYPT("saml_encrypt", "SAML 加密"); + + @JsonValue + private final String code; + /** + * name + */ + private final String desc; + + AppCertUsingType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + @Override + public String getDesc() { + return desc; + } + + @EnumConvert + public static AppCertUsingType getType(String code) { + AppCertUsingType[] values = values(); + for (AppCertUsingType type : values) { + if (String.valueOf(type.getCode()).equals(code)) { + return type; + } + } + return null; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AppProtocol.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AppProtocol.java new file mode 100644 index 00000000..29ba3a96 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AppProtocol.java @@ -0,0 +1,88 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.common.enums.BaseEnum; +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 应用模板 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/11/29 22:27 + */ +public enum AppProtocol implements BaseEnum { + /** + * OIDC + */ + OIDC("oidc", "OIDC"), + + /** + * SAML + */ + SAML2("saml2", "SAML 2.0"), + + /** + * CAS + */ + CAS("cas", "CAS"), + + /** + * JWT + */ + JWT("jwt", "JWT"), + + /** + * FORM表单 + */ + FORM("form", "表单代填"); + + @JsonValue + private final String code; + /** + * name + */ + private final String desc; + + AppProtocol(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + @Override + public String getDesc() { + return desc; + } + + @EnumConvert + public static AppProtocol getType(String code) { + AppProtocol[] values = values(); + for (AppProtocol source : values) { + if (String.valueOf(source.getCode()).equals(code)) { + return source; + } + } + return null; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AppType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AppType.java new file mode 100644 index 00000000..1b2d9b70 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AppType.java @@ -0,0 +1,101 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.common.enums.BaseEnum; +import cn.topiam.employee.common.enums.OrganizationType; +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 应用类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/22 20:21 + */ +public enum AppType implements BaseEnum { + + /** + * 定制应用 + */ + CUSTOM_MADE("custom_made", "定制应用"), + /** + * 标准应用 + */ + STANDARD("standard", "标准应用"), + /** + * 自研 + */ + SELF_DEVELOPED("self_developed", "自研应用"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + + AppType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + /** + * 获取来源 + * + * @param code {@link String} + * @return {@link OrganizationType} + */ + @EnumConvert + public static AppType getType(String code) { + AppType[] values = values(); + for (AppType source : values) { + if (String.valueOf(source.getCode()).equals(code)) { + return source; + } + } + return null; + } + + public static AppType getName(String name) { + AppType[] values = values(); + for (AppType source : values) { + if (source.name().equals(name)) { + return source; + } + } + return null; + } + + @Override + public String toString() { + return this.code; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AttributeNameFormat.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AttributeNameFormat.java new file mode 100644 index 00000000..2877bea9 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AttributeNameFormat.java @@ -0,0 +1,72 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app; + +import org.opensaml.saml.saml2.core.Attribute; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 属性 AttributeNameFormat + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/31 22:02 + */ +public enum AttributeNameFormat { + /** + * URI for unspecified name format. + */ + UNSPECIFIED(Attribute.UNSPECIFIED), + /** + * basic + */ + BASIC(Attribute.BASIC), + /** + * URI_REFERENCE + */ + URI_REFERENCE(Attribute.URI_REFERENCE); + + @JsonValue + private final String value; + + AttributeNameFormat(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @EnumConvert + public static AttributeNameFormat getType(String code) { + AttributeNameFormat[] values = values(); + for (AttributeNameFormat status : values) { + if (String.valueOf(status.getValue()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return value; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AuthnContextClassRefType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AuthnContextClassRefType.java new file mode 100644 index 00000000..c489629e --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AuthnContextClassRefType.java @@ -0,0 +1,74 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app; + +import org.opensaml.saml.saml2.core.AuthnContext; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * Authn 上下文类引用类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/4 23:44 + */ +public enum AuthnContextClassRefType { + + /** + * URI for unspecified authentication context. + */ + UNSPECIFIED_AUTHN_CTX(AuthnContext.UNSPECIFIED_AUTHN_CTX), + /** + * URI for Password authentication context. + */ + PASSWORD_AUTHN_CTX(AuthnContext.PASSWORD_AUTHN_CTX), + + /** + * URI for Password Protected Transport authentication context. + */ + PPT_AUTHN_CTX(AuthnContext.PPT_AUTHN_CTX); + + @JsonValue + private final String value; + + AuthnContextClassRefType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @EnumConvert + public static AuthnContextClassRefType getType(String code) { + AuthnContextClassRefType[] values = values(); + for (AuthnContextClassRefType status : values) { + if (String.valueOf(status.getValue()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return value; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AuthorizationType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AuthorizationType.java new file mode 100644 index 00000000..42b9ba8f --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/AuthorizationType.java @@ -0,0 +1,85 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.common.enums.BaseEnum; +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * SSO 授权类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/27 23:25 + */ +public enum AuthorizationType implements BaseEnum { + /** + * 手动授权 + */ + AUTHORIZATION("authorization", "手动授权"), + /** + * 全员可访问 + */ + ALL_ACCESS("all_access", "全员可访问"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + + AuthorizationType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + /** + * 获取类型 + * + * @param code {@link String} + * @return {@link AuthorizationType} + */ + @EnumConvert + public static AuthorizationType getType(String code) { + AuthorizationType[] values = values(); + for (AuthorizationType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return this.code; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/InitLoginType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/InitLoginType.java new file mode 100644 index 00000000..79ebe84c --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/InitLoginType.java @@ -0,0 +1,85 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.common.enums.BaseEnum; +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * Sso 发起方 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/4 23:33 + */ +public enum InitLoginType implements BaseEnum { + /** + * 仅应用发起SSO,OIDC协议应用默认取值。当SAML应用指定为该方式时,InitLoginUrl必须指定。 + */ + APP("only_app_init_sso", "只允许应用发起"), + /** + * 支持门户或者应用发起SSO,SAML协议应用默认取值范围。当OIDC协议指定为该方式时,InitLoginUrl必须指定。 + */ + PORTAL_OR_APP("portal_or_app_init_sso", "支持门户和应用发起"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + + InitLoginType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + /** + * 获取类型 + * + * @param code {@link String} + * @return {@link InitLoginType} + */ + @EnumConvert + public static InitLoginType getType(String code) { + InitLoginType[] values = values(); + for (InitLoginType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return this.code; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlAttributeStatementValueType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlAttributeStatementValueType.java new file mode 100644 index 00000000..f6f3af91 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlAttributeStatementValueType.java @@ -0,0 +1,104 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.common.enums.BaseEnum; +import cn.topiam.employee.support.web.converter.EnumConvert; +import static org.apache.commons.text.StringSubstitutor.DEFAULT_VAR_END; +import static org.apache.commons.text.StringSubstitutor.DEFAULT_VAR_START; + +/** + * Saml 2 AttributeStatementValue 类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/22 23:26 + */ +public enum SamlAttributeStatementValueType implements BaseEnum { + /** + * 手机号 + */ + PHONE("user.phone", + DEFAULT_VAR_START + + "user.phone" + + DEFAULT_VAR_END), + /** + * 用户名 + */ + USERNAME("user.username", + DEFAULT_VAR_START + "user.username" + + DEFAULT_VAR_END), + /** + * 昵称 + */ + NICK_NAME("user.nickName", + DEFAULT_VAR_START + "user.nickName" + + DEFAULT_VAR_END), + /** + * 邮箱 + */ + EMAIL("user.email", + DEFAULT_VAR_START + + "user.email" + + DEFAULT_VAR_END), + /** + * 应用用户名 + */ + APP_USERNAME("app_user.username", + DEFAULT_VAR_START + "app_user.username" + + DEFAULT_VAR_END); + + @JsonValue + private final String code; + private final String expression; + + SamlAttributeStatementValueType(String code, String expression) { + this.code = code; + this.expression = expression; + } + + @EnumConvert + public static SamlAttributeStatementValueType getType(String code) { + SamlAttributeStatementValueType[] values = values(); + for (SamlAttributeStatementValueType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } + + public String getCode() { + return code; + } + + @Override + public String getDesc() { + return null; + } + + public String getExpression() { + return expression; + } + + @Override + public String toString() { + return code; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlEncryptAssertAlgorithmType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlEncryptAssertAlgorithmType.java new file mode 100644 index 00000000..c40f64ad --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlEncryptAssertAlgorithmType.java @@ -0,0 +1,64 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; +import static org.opensaml.xmlsec.encryption.support.EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES256; + +/** + * Saml 加密算法类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/22 23:37 + */ +public enum SamlEncryptAssertAlgorithmType { + + /** + * Block Encryption - REQUIRED AES-256. + */ + RSA_SHA256(ALGO_ID_BLOCKCIPHER_AES256); + + @JsonValue + private final String value; + + SamlEncryptAssertAlgorithmType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @EnumConvert + public static SamlEncryptAssertAlgorithmType getType(String code) { + SamlEncryptAssertAlgorithmType[] values = values(); + for (SamlEncryptAssertAlgorithmType status : values) { + if (String.valueOf(status.getValue()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return value; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlNameIdFormatType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlNameIdFormatType.java new file mode 100644 index 00000000..01d87f28 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlNameIdFormatType.java @@ -0,0 +1,80 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app; + +import org.opensaml.saml.saml2.core.NameIDType; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * NameIdFormatType + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/22 23:33 + */ +public enum SamlNameIdFormatType { + + /** + * URI for unspecified name format. + */ + UNSPECIFIED(NameIDType.UNSPECIFIED), + + /** + * URI for persistent name format. + */ + PERSISTENT(NameIDType.PERSISTENT), + + /** + * URI for transient name format. + */ + TRANSIENT(NameIDType.TRANSIENT), + + /** + * URI for email name format. + */ + EMAIL(NameIDType.EMAIL); + + @JsonValue + private final String value; + + SamlNameIdFormatType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @EnumConvert + public static SamlNameIdFormatType getType(String code) { + SamlNameIdFormatType[] values = values(); + for (SamlNameIdFormatType status : values) { + if (String.valueOf(status.getValue()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return value; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlNameIdValueType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlNameIdValueType.java new file mode 100644 index 00000000..57495e8e --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlNameIdValueType.java @@ -0,0 +1,78 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * NameID + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/22 23:49 + */ +public enum SamlNameIdValueType { + /** + * 用户名 + */ + USER_USERNAME("user.username"), + /** + * 姓名 + */ + USER_FULL_NAME("user.fullName"), + /** + * 昵称 + */ + USER_NICK_NAME("user.nickName"), + /** + * 邮箱 + */ + USER_EMAIL("user.email"), + /** + * 应用账户 + */ + APP_USERNAME("app_user.username"); + + @JsonValue + private final String code; + + SamlNameIdValueType(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + @EnumConvert + public static SamlNameIdValueType getType(String code) { + SamlNameIdValueType[] values = values(); + for (SamlNameIdValueType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return code; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlParseMetadataType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlParseMetadataType.java new file mode 100644 index 00000000..3c259933 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlParseMetadataType.java @@ -0,0 +1,35 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app; + +/** + * Saml 2 解析元数据类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/30 22:43 + */ +public enum SamlParseMetadataType { + /** + * 文件 + */ + FILE, + /** + * URL + */ + URL +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlSignAssertAlgorithmType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlSignAssertAlgorithmType.java new file mode 100644 index 00000000..7a88303a --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlSignAssertAlgorithmType.java @@ -0,0 +1,64 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; + +/** + * Saml 签名使用的非对称算法 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/22 23:37 + */ +public enum SamlSignAssertAlgorithmType { + + /** + * Signature - Required RSAwithSHA256. + */ + RSA_SHA256(ALGO_ID_SIGNATURE_RSA_SHA256); + + @JsonValue + private final String value; + + SamlSignAssertAlgorithmType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @EnumConvert + public static SamlSignAssertAlgorithmType getType(String code) { + SamlSignAssertAlgorithmType[] values = values(); + for (SamlSignAssertAlgorithmType status : values) { + if (String.valueOf(status.getValue()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return value; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlSignResponseAlgorithmType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlSignResponseAlgorithmType.java new file mode 100644 index 00000000..8f80df57 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/SamlSignResponseAlgorithmType.java @@ -0,0 +1,64 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; +import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; + +/** + * Saml Response 使用的非对称算法 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/22 23:37 + */ +public enum SamlSignResponseAlgorithmType { + + /** + * Signature - Required RSAwithSHA256. + */ + RSA_SHA256(ALGO_ID_SIGNATURE_RSA_SHA256); + + @JsonValue + private final String value; + + SamlSignResponseAlgorithmType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @EnumConvert + public static SamlSignResponseAlgorithmType getType(String code) { + SamlSignResponseAlgorithmType[] values = values(); + for (SamlSignResponseAlgorithmType status : values) { + if (String.valueOf(status.getValue()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return value; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/AppCertUsingTypeConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/AppCertUsingTypeConverter.java new file mode 100644 index 00000000..53105ad0 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/AppCertUsingTypeConverter.java @@ -0,0 +1,69 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.app.AppCertUsingType; + +/** + * 应用证书使用类型转换器 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/11 23:48 + */ +@Converter(autoApply = true) +public class AppCertUsingTypeConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(AppCertUsingType attribute) { + if (Objects.isNull(attribute)) { + return null; + } + return attribute.getCode(); + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public AppCertUsingType convertToEntityAttribute(String dbData) { + return AppCertUsingType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/AppProtocolConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/AppProtocolConverter.java new file mode 100644 index 00000000..2499a9c6 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/AppProtocolConverter.java @@ -0,0 +1,67 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.app.AppProtocol; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/11 23:48 + */ +@Converter(autoApply = true) +public class AppProtocolConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(AppProtocol attribute) { + if (Objects.isNull(attribute)) { + return null; + } + return attribute.getCode(); + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public AppProtocol convertToEntityAttribute(String dbData) { + return AppProtocol.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/AppTypeConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/AppTypeConverter.java new file mode 100644 index 00000000..5626e4ff --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/AppTypeConverter.java @@ -0,0 +1,67 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.app.AppType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/11 23:48 + */ +@Converter(autoApply = true) +public class AppTypeConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(AppType attribute) { + if (Objects.isNull(attribute)) { + return null; + } + return attribute.getCode(); + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public AppType convertToEntityAttribute(String dbData) { + return AppType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlAttributeNameFormatConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlAttributeNameFormatConverter.java new file mode 100644 index 00000000..c474920e --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlAttributeNameFormatConverter.java @@ -0,0 +1,48 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.app.AttributeNameFormat; + +/** + * SamlNameIdFormatTypeConverter + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/22 23:33 + */ +@Converter(autoApply = true) +public class SamlAttributeNameFormatConverter implements + AttributeConverter { + @Override + public String convertToDatabaseColumn(AttributeNameFormat attribute) { + if (Objects.isNull(attribute)) { + return null; + } + return attribute.getValue(); + } + + @Override + public AttributeNameFormat convertToEntityAttribute(String dbData) { + return AttributeNameFormat.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlAttributeStatementValueTypeConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlAttributeStatementValueTypeConverter.java new file mode 100644 index 00000000..688300a7 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlAttributeStatementValueTypeConverter.java @@ -0,0 +1,48 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.app.SamlAttributeStatementValueType; + +/** + * Saml 2 AttributeStatementValue 类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/22 23:26 + */ +@Converter(autoApply = true) +public class SamlAttributeStatementValueTypeConverter implements + AttributeConverter { + @Override + public String convertToDatabaseColumn(SamlAttributeStatementValueType attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + @Override + public SamlAttributeStatementValueType convertToEntityAttribute(String dbData) { + return SamlAttributeStatementValueType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlAuthnContextClassRefTypeConvert.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlAuthnContextClassRefTypeConvert.java new file mode 100644 index 00000000..619f6960 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlAuthnContextClassRefTypeConvert.java @@ -0,0 +1,47 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.app.AuthnContextClassRefType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/5/22 23:26 + */ +@Converter(autoApply = true) +public class SamlAuthnContextClassRefTypeConvert implements + AttributeConverter { + + @Override + public String convertToDatabaseColumn(AuthnContextClassRefType attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getValue(); + } + return null; + } + + @Override + public AuthnContextClassRefType convertToEntityAttribute(String dbData) { + return AuthnContextClassRefType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlEncryptAssertAlgorithmTypeConvert.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlEncryptAssertAlgorithmTypeConvert.java new file mode 100644 index 00000000..6ae8dc5b --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlEncryptAssertAlgorithmTypeConvert.java @@ -0,0 +1,49 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.app.SamlEncryptAssertAlgorithmType; + +/** + * Saml 加密断言算法类型转换 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/22 23:26 + */ +@Converter(autoApply = true) +public class SamlEncryptAssertAlgorithmTypeConvert implements + AttributeConverter { + + @Override + public String convertToDatabaseColumn(SamlEncryptAssertAlgorithmType attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getValue(); + } + return null; + } + + @Override + public SamlEncryptAssertAlgorithmType convertToEntityAttribute(String dbData) { + return SamlEncryptAssertAlgorithmType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlNameIdFormatTypeConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlNameIdFormatTypeConverter.java new file mode 100644 index 00000000..c9fbeb00 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlNameIdFormatTypeConverter.java @@ -0,0 +1,48 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.app.SamlNameIdFormatType; + +/** + * SamlNameIdFormatTypeConverter + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/22 23:33 + */ +@Converter(autoApply = true) +public class SamlNameIdFormatTypeConverter implements + AttributeConverter { + @Override + public String convertToDatabaseColumn(SamlNameIdFormatType attribute) { + if (Objects.isNull(attribute)) { + return null; + } + return attribute.getValue(); + } + + @Override + public SamlNameIdFormatType convertToEntityAttribute(String dbData) { + return SamlNameIdFormatType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlNameIdTypeConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlNameIdTypeConverter.java new file mode 100644 index 00000000..01eddda9 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlNameIdTypeConverter.java @@ -0,0 +1,45 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.app.SamlNameIdValueType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/5/22 23:25 + */ +@Converter(autoApply = true) +public class SamlNameIdTypeConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(SamlNameIdValueType attribute) { + if (Objects.isNull(attribute)) { + return null; + } + return attribute.getCode(); + } + + @Override + public SamlNameIdValueType convertToEntityAttribute(String dbData) { + return SamlNameIdValueType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlSignAssertAlgorithmTypeConvert.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlSignAssertAlgorithmTypeConvert.java new file mode 100644 index 00000000..158fdf92 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlSignAssertAlgorithmTypeConvert.java @@ -0,0 +1,47 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.app.SamlSignAssertAlgorithmType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/5/22 23:26 + */ +@Converter(autoApply = true) +public class SamlSignAssertAlgorithmTypeConvert implements + AttributeConverter { + + @Override + public String convertToDatabaseColumn(SamlSignAssertAlgorithmType attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getValue(); + } + return null; + } + + @Override + public SamlSignAssertAlgorithmType convertToEntityAttribute(String dbData) { + return SamlSignAssertAlgorithmType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlSignResponseAlgorithmTypeConvert.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlSignResponseAlgorithmTypeConvert.java new file mode 100644 index 00000000..f030cdb1 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SamlSignResponseAlgorithmTypeConvert.java @@ -0,0 +1,47 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.app.SamlSignResponseAlgorithmType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/5/22 23:26 + */ +@Converter(autoApply = true) +public class SamlSignResponseAlgorithmTypeConvert implements + AttributeConverter { + + @Override + public String convertToDatabaseColumn(SamlSignResponseAlgorithmType attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getValue(); + } + return null; + } + + @Override + public SamlSignResponseAlgorithmType convertToEntityAttribute(String dbData) { + return SamlSignResponseAlgorithmType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SsoInitiatorConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SsoInitiatorConverter.java new file mode 100644 index 00000000..e64e2f12 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SsoInitiatorConverter.java @@ -0,0 +1,67 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.app.InitLoginType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/11 23:48 + */ +@Converter(autoApply = true) +public class SsoInitiatorConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(InitLoginType attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public InitLoginType convertToEntityAttribute(String dbData) { + return InitLoginType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SsoScopeConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SsoScopeConverter.java new file mode 100644 index 00000000..87fc2366 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/app/converter/SsoScopeConverter.java @@ -0,0 +1,67 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.app.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.app.AuthorizationType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/11 23:48 + */ +@Converter(autoApply = true) +public class SsoScopeConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(AuthorizationType attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public AuthorizationType convertToEntityAttribute(String dbData) { + return AuthorizationType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/DataOriginConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/DataOriginConverter.java new file mode 100644 index 00000000..213522d3 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/DataOriginConverter.java @@ -0,0 +1,67 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.DataOrigin; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/11 23:48 + */ +@Converter(autoApply = true) +public class DataOriginConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(DataOrigin attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public DataOrigin convertToEntityAttribute(String dbData) { + return DataOrigin.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/IdentityProviderCategoryConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/IdentityProviderCategoryConverter.java new file mode 100644 index 00000000..1ace4051 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/IdentityProviderCategoryConverter.java @@ -0,0 +1,68 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.IdentityProviderCategory; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/11 19:42 + */ +@Converter(autoApply = true) +public class IdentityProviderCategoryConverter implements + AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(IdentityProviderCategory attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public IdentityProviderCategory convertToEntityAttribute(String dbData) { + return IdentityProviderCategory.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/IdentityProviderTypeConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/IdentityProviderTypeConverter.java new file mode 100644 index 00000000..2857b423 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/IdentityProviderTypeConverter.java @@ -0,0 +1,68 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.IdentityProviderType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/11 19:42 + */ +@Converter(autoApply = true) +public class IdentityProviderTypeConverter implements + AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(IdentityProviderType attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public IdentityProviderType convertToEntityAttribute(String dbData) { + return IdentityProviderType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/LanguageConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/LanguageConverter.java new file mode 100644 index 00000000..7bdd26f4 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/LanguageConverter.java @@ -0,0 +1,69 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.Language; + +/** + * LanguageConverter + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/5 21:49 + */ +@Converter(autoApply = true) +public class LanguageConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(Language attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getLocale(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public Language convertToEntityAttribute(String dbData) { + return Language.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/MailTypeConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/MailTypeConverter.java new file mode 100644 index 00000000..ae50e520 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/MailTypeConverter.java @@ -0,0 +1,69 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.MailType; + +/** + * MailTemplateTypeReadingConverter + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/5 21:49 + */ +@Converter(autoApply = true) +public class MailTypeConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(MailType attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public MailType convertToEntityAttribute(String dbData) { + return MailType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/MessageCategoryConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/MessageCategoryConverter.java new file mode 100644 index 00000000..5dd2b7db --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/MessageCategoryConverter.java @@ -0,0 +1,69 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.MessageCategory; + +/** + * MessageCategoryConverter + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/5 21:49 + */ +@Converter(autoApply = true) +public class MessageCategoryConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(MessageCategory attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public MessageCategory convertToEntityAttribute(String dbData) { + return MessageCategory.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/MfaMannerConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/MfaMannerConverter.java new file mode 100644 index 00000000..0b109343 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/MfaMannerConverter.java @@ -0,0 +1,69 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.MfaFactor; + +/** + * MfaManner Converter + * + * @author TopIAM + * Created by support@topiam.cn on 2022/7/19 23:04 + */ +@Converter(autoApply = true) +public class MfaMannerConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(MfaFactor attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public MfaFactor convertToEntityAttribute(String dbData) { + return MfaFactor.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/MfaModeConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/MfaModeConverter.java new file mode 100644 index 00000000..ab1e0bc5 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/MfaModeConverter.java @@ -0,0 +1,69 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.MfaMode; + +/** + * MFA Converter + * + * @author TopIAM + * Created by support@topiam.cn on 2022/7/19 23:04 + */ +@Converter(autoApply = true) +public class MfaModeConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(MfaMode attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public MfaMode convertToEntityAttribute(String dbData) { + return MfaMode.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/OrganizationTypeConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/OrganizationTypeConverter.java new file mode 100644 index 00000000..922ccc81 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/OrganizationTypeConverter.java @@ -0,0 +1,67 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.OrganizationType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/11 23:49 + */ +@Converter(autoApply = true) +public class OrganizationTypeConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(OrganizationType attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public OrganizationType convertToEntityAttribute(String dbData) { + return OrganizationType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/PermissionActionConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/PermissionActionConverter.java new file mode 100644 index 00000000..019fbf84 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/PermissionActionConverter.java @@ -0,0 +1,69 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.PermissionActionType; + +/** + * PermissionActionConverter + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/10 22:51 + */ +@Converter(autoApply = true) +public class PermissionActionConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(PermissionActionType attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public PermissionActionType convertToEntityAttribute(String dbData) { + return PermissionActionType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/PolicyEffectConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/PolicyEffectConverter.java new file mode 100644 index 00000000..9b767fb1 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/PolicyEffectConverter.java @@ -0,0 +1,67 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.PolicyEffect; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2021/11/4 19:07 + */ +@Converter(autoApply = true) +public class PolicyEffectConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(PolicyEffect attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public PolicyEffect convertToEntityAttribute(String dbData) { + return PolicyEffect.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/PolicyObjectTypeConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/PolicyObjectTypeConverter.java new file mode 100644 index 00000000..707babaa --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/PolicyObjectTypeConverter.java @@ -0,0 +1,67 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.PolicyObjectType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2021/11/4 19:07 + */ +@Converter(autoApply = true) +public class PolicyObjectTypeConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(PolicyObjectType attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public PolicyObjectType convertToEntityAttribute(String dbData) { + return PolicyObjectType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/PolicySubjectConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/PolicySubjectConverter.java new file mode 100644 index 00000000..2caed30d --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/PolicySubjectConverter.java @@ -0,0 +1,69 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.PolicySubjectType; + +/** + * 规则效果 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/4 19:07 + */ +@Converter(autoApply = true) +public class PolicySubjectConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(PolicySubjectType attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public PolicySubjectType convertToEntityAttribute(String dbData) { + return PolicySubjectType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/SmsTypeConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/SmsTypeConverter.java new file mode 100644 index 00000000..0b76f8cb --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/SmsTypeConverter.java @@ -0,0 +1,69 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.SmsType; + +/** + * SendScenesConverter + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/5 21:49 + */ +@Converter(autoApply = true) +public class SmsTypeConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(SmsType attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public SmsType convertToEntityAttribute(String dbData) { + return SmsType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/SyncStatusConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/SyncStatusConverter.java new file mode 100644 index 00000000..c877a616 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/SyncStatusConverter.java @@ -0,0 +1,68 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.SyncStatus; + +/** + * SyncStatusConverter + * + * @author TopIAM + * Created by support@topiam.cn on 2022/10/8 21:11 + */ +@Converter(autoApply = true) +public class SyncStatusConverter implements AttributeConverter { + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(SyncStatus attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public SyncStatus convertToEntityAttribute(String dbData) { + return SyncStatus.getStatus(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/TriggerTypeConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/TriggerTypeConverter.java new file mode 100644 index 00000000..15842a91 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/TriggerTypeConverter.java @@ -0,0 +1,66 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.TriggerType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/11 23:49 + */ +@Converter(autoApply = true) +public class TriggerTypeConverter implements AttributeConverter { + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(TriggerType attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public TriggerType convertToEntityAttribute(String dbData) { + return TriggerType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/UserIdTypeConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/UserIdTypeConverter.java new file mode 100644 index 00000000..4eeecd63 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/UserIdTypeConverter.java @@ -0,0 +1,68 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.UserIdType; + +/** + * 证件类型 + * @author TopIAM + * Created by support@topiam.cn on 2020/12/11 23:48 + */ +@Converter(autoApply = true) +public class UserIdTypeConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(UserIdType attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public UserIdType convertToEntityAttribute(String dbData) { + return UserIdType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/UserStatusConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/UserStatusConverter.java new file mode 100644 index 00000000..8a5b5f66 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/UserStatusConverter.java @@ -0,0 +1,67 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.UserStatus; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/11 23:49 + */ +@Converter(autoApply = true) +public class UserStatusConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(UserStatus attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public UserStatus convertToEntityAttribute(String dbData) { + return UserStatus.getStatus(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/UserTypeConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/UserTypeConverter.java new file mode 100644 index 00000000..6d22f4a2 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/UserTypeConverter.java @@ -0,0 +1,67 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.UserType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2021/11/10 23:02 + */ +@Converter(autoApply = true) +public class UserTypeConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(UserType attribute) { + if (Objects.isNull(attribute)) { + return null; + } + return attribute.getCode(); + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public UserType convertToEntityAttribute(String dbData) { + return UserType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/package-info.java new file mode 100644 index 00000000..42d0e07b --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/converter/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.converter; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/identityprovider/IdentitySourceProvider.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/identityprovider/IdentitySourceProvider.java new file mode 100644 index 00000000..5d008ca5 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/identityprovider/IdentitySourceProvider.java @@ -0,0 +1,94 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.identityprovider; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.common.enums.BaseEnum; +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 身份源提供商 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/13 22:18 + */ +public enum IdentitySourceProvider implements BaseEnum { + /** + * 钉钉身份源 + */ + DINGTALK("dingtalk", "钉钉", + "钉钉(Ding Talk)是阿里巴巴集团打造的企业级智能移动办公平台,是数字经济时代的企业组织协同办公和应用开发平台。"), + + /** + * 企业微信 + */ + WECHAT_WORK("wechat_work", "企业微信", + "企业微信是腾讯微信团队打造的企业通讯与办公工具,具有与微信一致的沟通体验,丰富的OA应用,和连接微信生态的能力,可帮助企业连接内部、连接生态伙伴、连接消费者。专业协作、安全管理、人即服务。"), + /** + * 飞书 + */ + FEISHU("feishu", "飞书", + "飞书是字节跳动于2016年自研的新一代一站式协作平台,是保障字节跳动全球数万人高效协作的办公工具。飞书将即时沟通、日历、云文档、云盘和工作台深度整合,通过开放兼容的平台,让成员在一处即可实现高效的沟通和流畅的协作,全方位提升企业效率。"), + + /** + * AD + */ + WINDOWS_AD("windows-ad", "Windows AD", + "使用Windows AD身份源同步数据"), + + /** + * LDAP + */ + LDAP("ldap", "LDAP", + "LDAP 是轻量目录访问协议,你可以理解为一个树型结构,用来存储用户和组织信息的数据库"); + + @JsonValue + private final String code; + private final String name; + private final String desc; + + IdentitySourceProvider(String code, String name, String desc) { + this.code = code; + this.name = name; + this.desc = desc; + } + + @EnumConvert + public static IdentitySourceProvider getType(String code) { + IdentitySourceProvider[] values = values(); + for (IdentitySourceProvider value : values) { + if (value.getCode().equals(code)) { + return value; + } + } + return null; + } + + public String getCode() { + return code; + } + + public String getName() { + return name; + } + + public String getDesc() { + return desc; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/identityprovider/converter/IdentitySourceProviderConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/identityprovider/converter/IdentitySourceProviderConverter.java new file mode 100644 index 00000000..3384548d --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/identityprovider/converter/IdentitySourceProviderConverter.java @@ -0,0 +1,70 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.identityprovider.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.identityprovider.IdentitySourceProvider; + +/** + * 身份源提供商 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/13 22:18 + */ +@Converter(autoApply = true) +public class IdentitySourceProviderConverter implements + AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(IdentitySourceProvider attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public IdentitySourceProvider convertToEntityAttribute(String dbData) { + return IdentitySourceProvider.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/identitysource/IdentitySourceActionType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/identitysource/IdentitySourceActionType.java new file mode 100644 index 00000000..bc59f4ea --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/identitysource/IdentitySourceActionType.java @@ -0,0 +1,89 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.identitysource; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.common.enums.BaseEnum; +import cn.topiam.employee.common.enums.OrganizationType; +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * @author TopIAM + */ + +public enum IdentitySourceActionType implements BaseEnum { + + /** + * 新增 + */ + INSERT("insert", "新增"), + /** + * 修改 + */ + UPDATE("update", "修改"), + /** + * 删除 + */ + DELETE("delete", "删除"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + + IdentitySourceActionType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + /** + * 获取类型 + * + * @param code {@link String} + * @return {@link OrganizationType} + */ + @EnumConvert + public static IdentitySourceActionType getType(String code) { + IdentitySourceActionType[] values = values(); + for (IdentitySourceActionType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return this.code; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/identitysource/IdentitySourceObjectType.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/identitysource/IdentitySourceObjectType.java new file mode 100644 index 00000000..2fc2f1f4 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/identitysource/IdentitySourceObjectType.java @@ -0,0 +1,87 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.identitysource; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.common.enums.BaseEnum; +import cn.topiam.employee.common.enums.OrganizationType; +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 对象类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/9 21:04 + */ +public enum IdentitySourceObjectType implements BaseEnum { + + /** + * 用户 + */ + USER("user", "用户"), + /** + * 组织 + */ + ORGANIZATION("organization", "组织"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + + IdentitySourceObjectType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + /** + * 获取类型 + * + * @param code {@link String} + * @return {@link OrganizationType} + */ + @EnumConvert + public static IdentitySourceObjectType getType(String code) { + IdentitySourceObjectType[] values = values(); + for (IdentitySourceObjectType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return this.code; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/identitysource/converter/IdentitySourceActionTypeConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/identitysource/converter/IdentitySourceActionTypeConverter.java new file mode 100644 index 00000000..9a574bd5 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/identitysource/converter/IdentitySourceActionTypeConverter.java @@ -0,0 +1,67 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.identitysource.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.identitysource.IdentitySourceActionType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/11 19:42 + */ +@Converter(autoApply = true) +public class IdentitySourceActionTypeConverter implements + AttributeConverter { + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(IdentitySourceActionType attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public IdentitySourceActionType convertToEntityAttribute(String dbData) { + return IdentitySourceActionType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/identitysource/converter/IdentitySourceObjectTypeConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/identitysource/converter/IdentitySourceObjectTypeConverter.java new file mode 100644 index 00000000..795da6bc --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/identitysource/converter/IdentitySourceObjectTypeConverter.java @@ -0,0 +1,67 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.identitysource.converter; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.enums.identitysource.IdentitySourceObjectType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/11 23:49 + */ +@Converter(autoApply = true) +public class IdentitySourceObjectTypeConverter implements + AttributeConverter { + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(IdentitySourceObjectType attribute) { + if (!Objects.isNull(attribute)) { + return attribute.getCode(); + } + return null; + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public IdentitySourceObjectType convertToEntityAttribute(String dbData) { + return IdentitySourceObjectType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/package-info.java new file mode 100644 index 00000000..17e9289d --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/setting/converter/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/setting/converter/package-info.java new file mode 100644 index 00000000..8a11dcd9 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/setting/converter/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.setting.converter; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/enums/setting/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/enums/setting/package-info.java new file mode 100644 index 00000000..f162a958 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/enums/setting/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.enums.setting; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/exception/BindMfaNotFoundSecretException.java b/eiam-common/src/main/java/cn/topiam/employee/common/exception/BindMfaNotFoundSecretException.java new file mode 100644 index 00000000..4d923ea7 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/exception/BindMfaNotFoundSecretException.java @@ -0,0 +1,34 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.exception; + +import org.springframework.http.HttpStatus; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 绑定MFA 不存在秘钥异常 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/8 22:42 + */ +public class BindMfaNotFoundSecretException extends TopIamException { + public BindMfaNotFoundSecretException() { + super("bind_mfa_not_found_secret_error", "绑定TOTP 不存在秘钥", HttpStatus.BAD_REQUEST); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/exception/InvalidMfaCodeException.java b/eiam-common/src/main/java/cn/topiam/employee/common/exception/InvalidMfaCodeException.java new file mode 100644 index 00000000..1912a6b0 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/exception/InvalidMfaCodeException.java @@ -0,0 +1,35 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.exception; + +import org.springframework.http.HttpStatus; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * InvalidMfaCodeException + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/11 19:15 + */ +public class InvalidMfaCodeException extends TopIamException { + + public InvalidMfaCodeException() { + super("invalid_mfa_code_error", "Mfa 代码无效", HttpStatus.BAD_REQUEST); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/exception/LoginOtpActionNotSupportException.java b/eiam-common/src/main/java/cn/topiam/employee/common/exception/LoginOtpActionNotSupportException.java new file mode 100644 index 00000000..7e041dab --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/exception/LoginOtpActionNotSupportException.java @@ -0,0 +1,32 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.exception; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 登录OTP action 不支持 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/7 23:24 + */ +public class LoginOtpActionNotSupportException extends TopIamException { + public LoginOtpActionNotSupportException() { + super("login_otp_not_support_error", "登录 OTP Action 不支持", DEFAULT_STATUS); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/exception/MailMessageSendException.java b/eiam-common/src/main/java/cn/topiam/employee/common/exception/MailMessageSendException.java new file mode 100644 index 00000000..eaebf1a0 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/exception/MailMessageSendException.java @@ -0,0 +1,58 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.exception; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 邮件消息发送失败 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/25 19:43 + */ +public class MailMessageSendException extends TopIamException { + /** + * Constructs a new runtime exception with the specified detail message. + * The cause is not initialized, and may subsequently be initialized by a + * call to {@link #initCause}. + * + * @param message the detail message. The detail message is saved for + * later retrieval by the {@link #getMessage()} method. + */ + public MailMessageSendException(String message) { + super(message); + } + + /** + * Constructs a new runtime exception with the specified detail message and + * cause.

Note that the detail message associated with + * {@code cause} is not automatically incorporated in + * this runtime exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public MailMessageSendException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/exception/MailProviderException.java b/eiam-common/src/main/java/cn/topiam/employee/common/exception/MailProviderException.java new file mode 100644 index 00000000..863b7e11 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/exception/MailProviderException.java @@ -0,0 +1,56 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.exception; + +import java.io.Serial; + +import org.springframework.http.HttpStatus; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 邮件服务异常 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/19 22:53 + */ +public class MailProviderException extends TopIamException { + @Serial + private static final long serialVersionUID = 6249098979022610064L; + + public MailProviderException(String msg, Throwable t) { + super(msg, t); + } + + public MailProviderException(String msg) { + super(msg); + } + + public MailProviderException(String msg, HttpStatus status) { + super(msg, status); + } + + public MailProviderException(String error, String description, HttpStatus status) { + super(error, description, status); + } + + public MailProviderException(Throwable cause, String error, String description, + HttpStatus status) { + super(cause, error, description, status); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/exception/MailTemplateException.java b/eiam-common/src/main/java/cn/topiam/employee/common/exception/MailTemplateException.java new file mode 100644 index 00000000..925a1797 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/exception/MailTemplateException.java @@ -0,0 +1,56 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.exception; + +import java.io.Serial; + +import org.springframework.http.HttpStatus; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 邮件模板异常 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/18 21:36 + */ +public class MailTemplateException extends TopIamException { + @Serial + private static final long serialVersionUID = -6497956209061617684L; + + public MailTemplateException(String msg, Throwable t) { + super(msg, t); + } + + public MailTemplateException(String msg) { + super(msg); + } + + public MailTemplateException(String msg, HttpStatus status) { + super(msg, status); + } + + public MailTemplateException(String error, String description, HttpStatus status) { + super(error, description, status); + } + + public MailTemplateException(Throwable cause, String error, String description, + HttpStatus status) { + super(cause, error, description, status); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/exception/MessageSendException.java b/eiam-common/src/main/java/cn/topiam/employee/common/exception/MessageSendException.java new file mode 100644 index 00000000..c41b7bbd --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/exception/MessageSendException.java @@ -0,0 +1,36 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.exception; + +import org.springframework.http.HttpStatus; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 消息 发送异常 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/7 23:03 + */ +public class MessageSendException extends TopIamException { + + public MessageSendException(Throwable throwable) { + super(throwable, "message_send_error", "发送短信消息失败", HttpStatus.INTERNAL_SERVER_ERROR); + } + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/exception/OtpSendException.java b/eiam-common/src/main/java/cn/topiam/employee/common/exception/OtpSendException.java new file mode 100644 index 00000000..20543143 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/exception/OtpSendException.java @@ -0,0 +1,34 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.exception; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * OTP 发送异常 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/7 23:03 + */ +public class OtpSendException extends TopIamException { + + public OtpSendException(String message) { + super("otp_send_error", message, DEFAULT_STATUS); + } + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/exception/PasswordValidatedFailException.java b/eiam-common/src/main/java/cn/topiam/employee/common/exception/PasswordValidatedFailException.java new file mode 100644 index 00000000..36be9323 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/exception/PasswordValidatedFailException.java @@ -0,0 +1,38 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.exception; + +import org.springframework.http.HttpStatus; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 密码验证异常 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/8 22:09 + */ +public class PasswordValidatedFailException extends TopIamException { + public PasswordValidatedFailException() { + super("password_validated_fail_error", "密码错误,身份验证失败", HttpStatus.BAD_REQUEST); + } + + public PasswordValidatedFailException(String description) { + super("password_validated_fail_error", description, HttpStatus.BAD_REQUEST); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/exception/PrepareBindMfaException.java b/eiam-common/src/main/java/cn/topiam/employee/common/exception/PrepareBindMfaException.java new file mode 100644 index 00000000..5ecbc560 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/exception/PrepareBindMfaException.java @@ -0,0 +1,35 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.exception; + +import org.springframework.http.HttpStatus; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 准备绑定MFA异常 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/4/9 23:41 + */ +public class PrepareBindMfaException extends TopIamException { + + public PrepareBindMfaException(String msg) { + super("prepare_bind_mfa_error", msg, HttpStatus.BAD_REQUEST); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/exception/UserNotFoundException.java b/eiam-common/src/main/java/cn/topiam/employee/common/exception/UserNotFoundException.java new file mode 100644 index 00000000..bd010be0 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/exception/UserNotFoundException.java @@ -0,0 +1,34 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.exception; + +import org.springframework.http.HttpStatus; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 未找到用户异常 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/8 22:09 + */ +public class UserNotFoundException extends TopIamException { + public UserNotFoundException() { + super("user_not_found_error", "未找到用户异常", HttpStatus.UNAUTHORIZED); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppAccountExistException.java b/eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppAccountExistException.java new file mode 100644 index 00000000..2720acd1 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppAccountExistException.java @@ -0,0 +1,33 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.exception.app; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 应用账户已存在 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/7/8 22:49 + */ +public class AppAccountExistException extends TopIamException { + + public AppAccountExistException() { + super("app_account_exist", "应用账户已存在", DEFAULT_STATUS); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppAccountNotExistException.java b/eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppAccountNotExistException.java new file mode 100644 index 00000000..e1135474 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppAccountNotExistException.java @@ -0,0 +1,33 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.exception.app; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 应用账户不存在异常 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/7/8 22:49 + */ +public class AppAccountNotExistException extends TopIamException { + + public AppAccountNotExistException() { + super("app_account_not_exist", "应用账户不存在", DEFAULT_STATUS); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppPolicyNotExistException.java b/eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppPolicyNotExistException.java new file mode 100644 index 00000000..30d53c48 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppPolicyNotExistException.java @@ -0,0 +1,32 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.exception.app; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 应用策略不存在异常 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/7/8 22:23 + */ +public class AppPolicyNotExistException extends TopIamException { + public AppPolicyNotExistException() { + super("app_policy_not_exist", "应用策略不存在", DEFAULT_STATUS); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppResourceNotExistException.java b/eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppResourceNotExistException.java new file mode 100644 index 00000000..65f443d7 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppResourceNotExistException.java @@ -0,0 +1,32 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.exception.app; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 应用资源不存在异常 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/7/8 22:23 + */ +public class AppResourceNotExistException extends TopIamException { + public AppResourceNotExistException() { + super("app_resource_not_exist", "应用资源不存在", DEFAULT_STATUS); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppRoleNotExistException.java b/eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppRoleNotExistException.java new file mode 100644 index 00000000..5458e39c --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/exception/app/AppRoleNotExistException.java @@ -0,0 +1,32 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.exception.app; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 应用角色不存在异常 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/7/8 22:23 + */ +public class AppRoleNotExistException extends TopIamException { + public AppRoleNotExistException() { + super("app_role_not_exist", "应用角色不存在", DEFAULT_STATUS); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/exception/app/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/exception/app/package-info.java new file mode 100644 index 00000000..592b0d56 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/exception/app/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.exception.app; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/exception/handler/GlobalExceptionHandler.java b/eiam-common/src/main/java/cn/topiam/employee/common/exception/handler/GlobalExceptionHandler.java new file mode 100644 index 00000000..50d0362e --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/exception/handler/GlobalExceptionHandler.java @@ -0,0 +1,111 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.exception.handler; + +import javax.validation.ConstraintViolationException; + +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; +import org.springframework.http.HttpStatus; +import org.springframework.validation.BindException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.util.WebUtils; + +import cn.topiam.employee.support.exception.TopIamException; + +import lombok.AllArgsConstructor; + +/** + * 全局异常处理 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/20 20:55 + */ +@AllArgsConstructor +@RestControllerAdvice +public class GlobalExceptionHandler { + /** + * Exception + * + * @return {@link ModelAndView} + */ + @ExceptionHandler(value = Exception.class) + public ModelAndView exception(WebRequest request, Exception e) { + request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, + HttpStatus.INTERNAL_SERVER_ERROR.value(), WebRequest.SCOPE_REQUEST); + setExceptionAttribute(request, e); + return new ModelAndView(serverProperties.getError().getPath()); + } + + /** + * TopIamException + * + * @return {@link ModelAndView} + */ + @ExceptionHandler(value = TopIamException.class) + public ModelAndView topIamException(WebRequest request, TopIamException e) { + request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, e.getHttpStatus().value(), + WebRequest.SCOPE_REQUEST); + return new ModelAndView(serverProperties.getError().getPath()); + } + + /** + * BindException + * + * HandlerExceptionResolver 默认处理为400状态码,这里设置为500,并转发到异常处理页面。 + * + * @return {@link ModelAndView} + */ + @ExceptionHandler(value = BindException.class) + public ModelAndView bindException(WebRequest request) { + request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, HttpStatus.BAD_REQUEST.value(), + WebRequest.SCOPE_REQUEST); + return new ModelAndView(serverProperties.getError().getPath()); + } + + /** + * ConstraintViolationException + * + * HandlerExceptionResolver 默认处理为400状态码,这里设置为500,并转发到异常处理页面。 + * + * @return {@link ModelAndView} + */ + @ExceptionHandler(value = ConstraintViolationException.class) + public ModelAndView constraintViolationException(WebRequest request) { + request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, HttpStatus.BAD_REQUEST.value(), + WebRequest.SCOPE_REQUEST); + return new ModelAndView(serverProperties.getError().getPath()); + } + + private void setExceptionAttribute(WebRequest request, Exception exception) { + request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, exception, + WebRequest.SCOPE_REQUEST); + request.setAttribute(DefaultErrorAttributes.class.getName() + ".ERROR", exception, + WebRequest.SCOPE_REQUEST); + request.setAttribute(WebUtils.ERROR_EXCEPTION_TYPE_ATTRIBUTE, exception.getClass(), + WebRequest.SCOPE_REQUEST); + } + + /** + * ServerProperties + */ + private final ServerProperties serverProperties; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/geo/GeoLocation.java b/eiam-common/src/main/java/cn/topiam/employee/common/geo/GeoLocation.java new file mode 100644 index 00000000..bae1d1e5 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/geo/GeoLocation.java @@ -0,0 +1,107 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.geo; + +import java.io.Serial; +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import cn.topiam.employee.common.geo.maxmind.enums.GeoLocationProvider; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * GeoLocationResponse + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/27 19:31 + */ +@Data +@Accessors(chain = true) +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +public class GeoLocation implements Serializable { + + @Serial + private static final long serialVersionUID = 991484919483509517L; + + public GeoLocation() { + } + + /** + * IP + */ + private String ip; + + /** + * continent code + */ + private String continentCode; + + /** + * continent Name + */ + private String continentName; + + /** + * 国家/地区code + */ + private String countryCode; + + /** + * 国家 + */ + private String countryName; + + /** + * 城市code + */ + private String cityCode; + + /** + * 城市 + */ + private String cityName; + + /** + * 省份code + */ + private String provinceCode; + + /** + * 省份 + */ + private String provinceName; + + /** + * 经度 WGS84坐标 + */ + private Double latitude; + + /** + * 维度 WGS84坐标 + */ + private Double longitude; + + /** + * 提供商 + */ + private GeoLocationProvider provider; + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/geo/GeoLocationProviderConfig.java b/eiam-common/src/main/java/cn/topiam/employee/common/geo/GeoLocationProviderConfig.java new file mode 100644 index 00000000..bbb714a7 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/geo/GeoLocationProviderConfig.java @@ -0,0 +1,59 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.geo; + +import javax.validation.constraints.NotNull; + +import cn.topiam.employee.common.geo.maxmind.enums.GeoLocationProvider; + +import lombok.Data; + +/** + * MaxmindProviderSetting + * + * @author TopIAM + * Created by support@topiam.cn on 2021/10/1 19:10 + */ +@Data +public class GeoLocationProviderConfig { + + public GeoLocationProviderConfig() { + } + + public GeoLocationProviderConfig(GeoLocationProvider provider, GeoLocationConfig config) { + this.provider = provider; + this.config = config; + } + + /** + * 平台 + */ + @NotNull(message = "平台不能为空") + private GeoLocationProvider provider; + + /** + * 配置 + */ + private GeoLocationConfig config; + + @Data + public static class GeoLocationConfig { + private static final long serialVersionUID = 5611656522133230183L; + } + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/geo/GeoLocationService.java b/eiam-common/src/main/java/cn/topiam/employee/common/geo/GeoLocationService.java new file mode 100644 index 00000000..bf91daca --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/geo/GeoLocationService.java @@ -0,0 +1,42 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.geo; + +/** + * Geo + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/27 19:20 + */ +public interface GeoLocationService { + + /** + * 获取地理库文件 + * + */ + void download(); + + /** + * 获取地理位置 + * + * @param remote {@link String} + * @return {@link GeoLocationService} + */ + GeoLocation getGeoLocation(String remote); + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/geo/NoneGeoLocationServiceImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/geo/NoneGeoLocationServiceImpl.java new file mode 100644 index 00000000..c417ab25 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/geo/NoneGeoLocationServiceImpl.java @@ -0,0 +1,46 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.geo; + +/** + * None + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/27 19:20 + */ +public class NoneGeoLocationServiceImpl implements GeoLocationService { + + /** + * 获取地理库文件 + */ + @Override + public void download() { + + } + + /** + * 获取地理位置 + * + * @param remote {@link String} + * @return {@link GeoLocationService} + */ + @Override + public GeoLocation getGeoLocation(String remote) { + return new GeoLocation().setIp(remote); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/MaxmindGeoLocationServiceImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/MaxmindGeoLocationServiceImpl.java new file mode 100644 index 00000000..09103196 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/MaxmindGeoLocationServiceImpl.java @@ -0,0 +1,259 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.geo.maxmind; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Objects; + +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.ResourceAccessException; +import org.springframework.web.client.RestTemplate; + +import com.maxmind.db.CHMCache; +import com.maxmind.geoip2.DatabaseReader; +import com.maxmind.geoip2.model.CityResponse; +import com.maxmind.geoip2.record.*; + +import cn.topiam.employee.common.geo.GeoLocation; +import cn.topiam.employee.common.geo.GeoLocationService; +import cn.topiam.employee.support.constant.EiamConstants; +import cn.topiam.employee.support.util.IpUtils; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import dev.failsafe.Failsafe; +import dev.failsafe.RetryPolicy; +import static cn.topiam.employee.common.geo.maxmind.enums.GeoLocationProvider.MAXMIND; + +/** + * GeoIp + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/26 19:26 + */ +@Slf4j +@Getter +public class MaxmindGeoLocationServiceImpl implements GeoLocationService { + + private final DatabaseReader reader; + private final MaxmindProviderConfig maxmindProviderConfig; + private final RestTemplate restTemplate; + private final Integer MAX_RETRIES = 1999999999; + + public MaxmindGeoLocationServiceImpl(MaxmindProviderConfig maxmindProviderConfig, + RestTemplate restTemplate) throws IOException { + this.maxmindProviderConfig = maxmindProviderConfig; + this.restTemplate = restTemplate; + download(); + this.reader = new DatabaseReader.Builder(new File(EiamConstants.IPADDRESS_FILE_PATH)) + .withCache(new CHMCache()).locales(List.of("zh-CN")).build(); + } + + /** + * 获取地理位置 + * + * @param remote {@link String} + * @return {@link String} + */ + @Override + public GeoLocation getGeoLocation(String remote) { + if (IpUtils.isInternalIp(remote)) { + GeoLocation geoLocation = new GeoLocation(); + geoLocation.setIp(remote); + return geoLocation; + } + try { + CityResponse cityResponse = this.reader.tryCity(InetAddress.getByName(remote)) + .orElseThrow(); + // 获取国家信息 + Country country = cityResponse.getCountry(); + //省份信息 + Subdivision subdivision = cityResponse.getMostSpecificSubdivision(); + // 城市信息 + City city = cityResponse.getCity(); + Location location = cityResponse.getLocation(); + if (Objects.isNull(location)) { + return null; + } + //大陆信息 + Continent continent = cityResponse.getContinent(); + //@formatter:off + return new GeoLocation() + .setIp(remote) + .setContinentCode(continent.getGeoNameId().toString()) + .setContinentName(continent.getName()) + .setCountryName(country.getName()) + .setCountryCode(country.getGeoNameId().toString()) + .setCityName(city.getName()) + .setCityCode(String.valueOf(city.getGeoNameId())) + .setProvinceName(subdivision.getName()) + .setProvinceCode(subdivision.getIsoCode()) + .setLongitude(location.getLongitude()) + .setLatitude(location.getLatitude()) + .setProvider(MAXMIND); + //@formatter:on + } catch (Exception e) { + log.error("获取IP地理位置发生异常 IP:[{}], 异常: {}", remote, e.getMessage()); + return new GeoLocation(); + } + } + + @Override + public void download() { + if (checkDbFileIsUpdate()) { + //@formatter:off + RetryPolicy retryPolicy = RetryPolicy.builder() + .handle(ResourceAccessException.class) + .withDelay(Duration.ofSeconds(1)) + .withMaxRetries(MAX_RETRIES) + .onFailure(event -> log.error("下载IP库发生网络异常")) + .onRetry(event -> log.error("下载IP库发生网络异常, 开始第: {} 次重试",event.getExecutionCount())) + .build(); + ResponseEntity bytes = Failsafe.with(retryPolicy).get(() -> restTemplate.exchange(String.format(MAXMIND.getDownloadUrl(), maxmindProviderConfig.getSessionKey()), HttpMethod.GET, null, byte[].class)); + //@formatter:on + File path = new File(EiamConstants.IPADDRESS_FILE_DIRECTORY); + try { + if (!path.exists()) { + if (!path.mkdirs()) { + throw new IOException("创建文件路径失败"); + } + } + File file = new File(EiamConstants.IPADDRESS_FILE_TAR); + if (!file.exists()) { + if (!file.createNewFile()) { + throw new IOException("创建IP库文件失败"); + } + } + try (FileOutputStream out = new FileOutputStream(file)) { + out.write(Objects.requireNonNull(bytes.getBody()), 0, bytes.getBody().length); + out.flush(); + } catch (Exception e) { + log.error("IP库文件写入异常: {}", e.getMessage()); + } + unTar(file, EiamConstants.IPADDRESS_FILE_DIRECTORY); + } catch (Exception e) { + log.error("下载IP库发生异常: {}", e.getMessage()); + } + } else { + log.debug("IP地理库无需更新"); + } + } + + /** + * 检查更新 + */ + public Boolean checkDbFileIsUpdate() { + //@formatter:off + RetryPolicy retryPolicy = RetryPolicy.builder() + .handle(ResourceAccessException.class) + .withDelay(Duration.ofSeconds(1)) + .withMaxRetries(MAX_RETRIES) + .onFailure(event -> log.error("检查IP库更新失败")) + .onRetry(event -> log.error("检查IP库更新发生网络异常, 开始第: {} 次重试",event.getExecutionCount())) + .build(); + return Failsafe.with(retryPolicy).get(() -> { + File ipDb = new File(EiamConstants.IPADDRESS_FILE_TAR); + if (ipDb.exists()) { + ResponseEntity sha256FileByte = restTemplate.exchange( + String.format(MAXMIND.getSha256Url(), + this.maxmindProviderConfig.getSessionKey()), + HttpMethod.GET, null, byte[].class); + File sha256File = new File(EiamConstants.SHA256_FILE_PATH); + FileUtils.writeByteArrayToFile(sha256File, + Objects.requireNonNull(sha256FileByte.getBody())); + String sha256 = FileUtils.readFileToString(sha256File, StandardCharsets.UTF_8); + return !checkFileSha256(ipDb, sha256.split(" ")[0]); + } + return true; + }); + //@formatter:on + } + + /** + * + * @param file tar.gz 文件 + * @param extractPath 要解压到的目录 + */ + public static void unTar(File file, String extractPath) throws Exception { + // decompressing *.tar.gz files to tar + TarArchiveInputStream fin = new TarArchiveInputStream( + new GzipCompressorInputStream(new FileInputStream(file))); + File extractFolder = new File(extractPath); + TarArchiveEntry entry; + // 将 tar 文件解压到 extractPath 目录下 + while ((entry = fin.getNextTarEntry()) != null) { + if (entry.isDirectory()) { + continue; + } + File curfile = new File(extractFolder, FilenameUtils.getName(entry.getName())); + File parent = curfile.getParentFile(); + if (!parent.exists()) { + if (!parent.mkdirs()) { + throw new IOException("Create file path exception"); + } + } + // 将文件写出到解压的目录 + IOUtils.copy(fin, new FileOutputStream(curfile)); + } + } + + /** + * 检查文件的SHA256 是否正确 + * + * @param file 文件 + * @param sha256 SHA256结果值 + */ + public static Boolean checkFileSha256(File file, String sha256) { + String sha256Hex; + FileInputStream inputStream = null; + try { + inputStream = new FileInputStream(file); + sha256Hex = DigestUtils.sha256Hex(inputStream); + if (sha256Hex.equals(sha256)) { + return true; + } + } catch (IOException e) { + log.error("SHA256检查文件完整性失败", e); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + log.error("InputStream close exception: {}", e.getMessage()); + } + } + } + return false; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/MaxmindProviderConfig.java b/eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/MaxmindProviderConfig.java new file mode 100644 index 00000000..9a03d5a6 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/MaxmindProviderConfig.java @@ -0,0 +1,40 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.geo.maxmind; + +import javax.validation.constraints.NotEmpty; + +import cn.topiam.employee.common.geo.GeoLocationProviderConfig; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * + * @author TopIAM + * Created by support@topiam.cn on 2022/7/23 20:33 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class MaxmindProviderConfig extends GeoLocationProviderConfig.GeoLocationConfig { + /** + * 密码 + */ + @NotEmpty(message = "密码不能为空") + private String sessionKey; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/UpdateMaxmindTaskConfiguration.java b/eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/UpdateMaxmindTaskConfiguration.java new file mode 100644 index 00000000..ee5194c0 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/UpdateMaxmindTaskConfiguration.java @@ -0,0 +1,61 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.geo.maxmind; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import cn.topiam.employee.common.geo.GeoLocationService; + +import lombok.RequiredArgsConstructor; + +/** + * 更新 maxmind 数据库 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/7 22:25 + */ +@Component +@RequiredArgsConstructor +public class UpdateMaxmindTaskConfiguration { + private final Logger logger = LoggerFactory.getLogger(UpdateMaxmindTaskConfiguration.class); + + /** + * 每天一点执行ip库文件更新 + */ + @Scheduled(cron = "0 0 1 * * ?") + public void update() { + try { + if (geoLocationService instanceof MaxmindGeoLocationServiceImpl) { + logger.info("执行IP库文件更新定时任务开始"); + MaxmindGeoLocationServiceImpl maxmindGeoLocation = (MaxmindGeoLocationServiceImpl) geoLocationService; + if (maxmindGeoLocation.checkDbFileIsUpdate()) { + maxmindGeoLocation.download(); + } + logger.info("执行IP库文件更新定时任务结束"); + } + } catch (Exception e) { + logger.error("执行IP库文件更新定时发生异常:{}", e.getMessage()); + } + } + + private final GeoLocationService geoLocationService; + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/enums/GeoLocationProvider.java b/eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/enums/GeoLocationProvider.java new file mode 100644 index 00000000..f01a4f61 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/enums/GeoLocationProvider.java @@ -0,0 +1,96 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.geo.maxmind.enums; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 地理位置提供商配置 + * + * @author TopIAM + */ +public enum GeoLocationProvider implements Serializable { + + /** + * maxmind + */ + MAXMIND("maxmind", "MAXMIND", + "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=%s&suffix=tar.gz", + "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=%s&suffix=tar.gz.sha256"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * 名称 + */ + private final String name; + /** + * 库文件下载地址 + */ + private final String downloadUrl; + /** + * sha256校验文件下载地址 + */ + private final String sha256Url; + + GeoLocationProvider(String code, String name, String downloadUrl, String sha256Url) { + this.code = code; + this.name = name; + this.downloadUrl = downloadUrl; + this.sha256Url = sha256Url; + } + + public String getCode() { + return code; + } + + public String getName() { + return name; + } + + public String getDownloadUrl() { + return downloadUrl; + } + + public String getSha256Url() { + return sha256Url; + } + + @EnumConvert + public static GeoLocationProvider getType(String code) { + GeoLocationProvider[] values = values(); + for (GeoLocationProvider status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + throw new NullPointerException("未找到该平台"); + } + + @Override + public String toString() { + return this.code; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/package-info.java new file mode 100644 index 00000000..ac575451 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/geo/maxmind/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.geo.maxmind; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/geo/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/geo/package-info.java new file mode 100644 index 00000000..4d973f7a --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/geo/package-info.java @@ -0,0 +1,24 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +/** + * geoip + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/26 19:27 + */ +package cn.topiam.employee.common.geo; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/geo/taobao/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/geo/taobao/package-info.java new file mode 100644 index 00000000..2053f23d --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/geo/taobao/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.geo.taobao; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/SendMessageException.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/SendMessageException.java new file mode 100644 index 00000000..c936c68e --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/SendMessageException.java @@ -0,0 +1,105 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message; + +import java.io.Serial; + +import cn.topiam.employee.common.message.enums.MessageType; + +/** + * 消息发送异常 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/19 22:53 + */ +public class SendMessageException extends RuntimeException { + @Serial + private static final long serialVersionUID = 6249098979022610064L; + private MessageType messageType; + + /** + * Constructs a new runtime exception with the specified detail message. + * The cause is not initialized, and may subsequently be initialized by a + * call to {@link #initCause}. + * + * @param message the detail message. The detail message is saved for + * later retrieval by the {@link #getMessage()} method. + */ + public SendMessageException(MessageType messageType, String message) { + super(message); + this.messageType = messageType; + } + + public MessageType getMessageType() { + return messageType; + } + + /** + * Constructs a new runtime exception with the specified detail message and + * cause.

Note that the detail message associated with + * {@code cause} is not automatically incorporated in + * this runtime exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public SendMessageException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new runtime exception with the specified cause and a + * detail message of (cause==null ? null : cause.toString()) + * (which typically contains the class and detail message of + * cause). This constructor is useful for runtime exceptions + * that are little more than wrappers for other throwables. + * + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public SendMessageException(Throwable cause) { + super(cause); + } + + /** + * Constructs a new runtime exception with the specified detail + * message, cause, suppression enabled or disabled, and writable + * stack trace enabled or disabled. + * + * @param message the detail message. + * @param cause the cause. (A {@code null} value is permitted, + * and indicates that the cause is nonexistent or unknown.) + * @param enableSuppression whether or not suppression is enabled + * or disabled + * @param writableStackTrace whether or not the stack trace should + * be writable + * @since 1.7 + */ + public SendMessageException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/SmsProviderException.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/SmsProviderException.java new file mode 100644 index 00000000..969231fd --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/SmsProviderException.java @@ -0,0 +1,94 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message; + +/** + * 邮件服务异常 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/19 22:53 + */ +public class SmsProviderException extends RuntimeException { + private static final long serialVersionUID = 6249098979022610064L; + + /** + * Constructs a new runtime exception with the specified detail message. + * The cause is not initialized, and may subsequently be initialized by a + * call to {@link #initCause}. + * + * @param message the detail message. The detail message is saved for + * later retrieval by the {@link #getMessage()} method. + */ + public SmsProviderException(String message) { + super(message); + } + + /** + * Constructs a new runtime exception with the specified detail message and + * cause.

Note that the detail message associated with + * {@code cause} is not automatically incorporated in + * this runtime exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public SmsProviderException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new runtime exception with the specified cause and a + * detail message of (cause==null ? null : cause.toString()) + * (which typically contains the class and detail message of + * cause). This constructor is useful for runtime exceptions + * that are little more than wrappers for other throwables. + * + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public SmsProviderException(Throwable cause) { + super(cause); + } + + /** + * Constructs a new runtime exception with the specified detail + * message, cause, suppression enabled or disabled, and writable + * stack trace enabled or disabled. + * + * @param message the detail message. + * @param cause the cause. (A {@code null} value is permitted, + * and indicates that the cause is nonexistent or unknown.) + * @param enableSuppression whether or not suppression is enabled + * or disabled + * @param writableStackTrace whether or not the stack trace should + * be writable + * @since 1.7 + */ + public SmsProviderException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/MailProvider.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/MailProvider.java new file mode 100644 index 00000000..a4d6012d --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/MailProvider.java @@ -0,0 +1,118 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.enums; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 邮件提供商配置 + * + * @author TopIAM + */ +public enum MailProvider implements Serializable { + + /** + * 自定义 + */ + CUSTOMIZE("customize", "自定义", null, null, null), + /** + * 腾讯企业邮 + */ + TENCENT("tencent", "腾讯企业邮", "smtp.exmail.qq.com", + 25, 465), + /** + * 阿里企业邮 + */ + ALIYUN("aliyun", "阿里企业邮", "smtp.mxhichina.com", + 25, 465), + /** + * 网易 + */ + NETEASE("netease", "网易企业邮", "smtp.163.com", 25, + 465); + + /** + * code + */ + @JsonValue + private final String code; + /** + * 名称 + */ + private final String name; + /** + * smtp 地址 + */ + private final String smtpUrl; + /** + * 端口 + */ + private final Integer port; + /** + * SSL 端口 + */ + private final Integer sslPort; + + MailProvider(String code, String name, String smtpUrl, Integer port, Integer sslPort) { + this.code = code; + this.name = name; + this.smtpUrl = smtpUrl; + this.port = port; + this.sslPort = sslPort; + } + + public String getCode() { + return code; + } + + public String getName() { + return name; + } + + @EnumConvert + public static MailProvider getType(String code) { + MailProvider[] values = values(); + for (MailProvider status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + throw new NullPointerException("未找到该平台"); + } + + public String getSmtpUrl() { + return smtpUrl; + } + + public Integer getPort() { + return port; + } + + public Integer getSslPort() { + return sslPort; + } + + @Override + public String toString() { + return this.code; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/MailSafetyType.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/MailSafetyType.java new file mode 100644 index 00000000..c5f33d54 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/MailSafetyType.java @@ -0,0 +1,86 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.enums; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 邮件安全方式 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/19 23:09 + */ +public enum MailSafetyType implements Serializable { + /** + * 无 + */ + None("none", "无"), + /** + * SSL + */ + SSL("ssl", "SSL"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * 描述 + */ + private final String desc; + + /** + * 构造 + * + * @param code {@link String} + * @param desc {@link String} + */ + MailSafetyType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + @EnumConvert + public static MailSafetyType getType(String code) { + MailSafetyType[] values = values(); + for (MailSafetyType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + throw new NullPointerException("未找到该类型"); + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + @Override + public String toString() { + return code; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/MessageType.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/MessageType.java new file mode 100644 index 00000000..d1371a08 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/MessageType.java @@ -0,0 +1,87 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.enums; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 消息类型 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/25 19:19 + */ +public enum MessageType implements Serializable { + /** + * 邮件 + */ + MAIL("mail", "邮件"), + /** + * 短信 + */ + SMS("sms", "短信"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + + MessageType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + /** + * 获取类型 + * + * @param code {@link String} + * @return {@link String} + */ + @EnumConvert + public static MessageType getType(String code) { + MessageType[] values = values(); + for (MessageType status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + return null; + } + + @Override + public String toString() { + return this.code; + } + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/SmsProvider.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/SmsProvider.java new file mode 100644 index 00000000..62cca1fc --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/SmsProvider.java @@ -0,0 +1,85 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.enums; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 短信平台 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/19 + */ +public enum SmsProvider implements Serializable { + + /** + * 阿里云 + */ + ALIYUN("aliyun", "阿里云"), + /** + * 腾讯云 + */ + TENCENT("tencent", "腾讯云"), + /** + * 七牛 + */ + QINIU("qiniu", "七牛"); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + + SmsProvider(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + @EnumConvert + public static SmsProvider getType(String code) { + SmsProvider[] values = values(); + for (SmsProvider status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + throw new NullPointerException("未找到该平台"); + } + + @Override + public String toString() { + return this.code; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/convert/MailProviderPlatformConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/convert/MailProviderPlatformConverter.java new file mode 100644 index 00000000..432437fd --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/convert/MailProviderPlatformConverter.java @@ -0,0 +1,69 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.enums.convert; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.message.enums.MailProvider; + +/** + * MailProviderPlatformReadingConverter + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/5 21:45 + */ +@Converter(autoApply = true) +public class MailProviderPlatformConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(MailProvider attribute) { + if (Objects.isNull(attribute)) { + return null; + } + return attribute.getCode(); + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public MailProvider convertToEntityAttribute(String dbData) { + return MailProvider.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/convert/MailSafetyTypeConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/convert/MailSafetyTypeConverter.java new file mode 100644 index 00000000..9f72722f --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/convert/MailSafetyTypeConverter.java @@ -0,0 +1,69 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.enums.convert; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.message.enums.MailSafetyType; + +/** + * ReadingConverter + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/5 20:39 + */ +@Converter(autoApply = true) +public class MailSafetyTypeConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(MailSafetyType attribute) { + if (Objects.isNull(attribute)) { + return null; + } + return attribute.getCode(); + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public MailSafetyType convertToEntityAttribute(String dbData) { + return MailSafetyType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/convert/MessageTypeConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/convert/MessageTypeConverter.java new file mode 100644 index 00000000..816ad53a --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/convert/MessageTypeConverter.java @@ -0,0 +1,69 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.enums.convert; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.message.enums.MessageType; + +/** + * MessageType + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/5 21:49 + */ +@Converter(autoApply = true) +public class MessageTypeConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(MessageType attribute) { + if (Objects.isNull(attribute)) { + return null; + } + return attribute.getCode(); + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public MessageType convertToEntityAttribute(String dbData) { + return MessageType.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/convert/SmsProviderConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/convert/SmsProviderConverter.java new file mode 100644 index 00000000..a154b938 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/enums/convert/SmsProviderConverter.java @@ -0,0 +1,69 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.enums.convert; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.message.enums.SmsProvider; + +/** + * SmsPlatformWritingConverter + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/5 21:52 + */ +@Converter(autoApply = true) +public class SmsProviderConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(SmsProvider attribute) { + if (Objects.isNull(attribute)) { + return null; + } + return attribute.getCode(); + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public SmsProvider convertToEntityAttribute(String dbData) { + return SmsProvider.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/mail/DefaultMailProviderSendImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/mail/DefaultMailProviderSendImpl.java new file mode 100644 index 00000000..f60cc45b --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/mail/DefaultMailProviderSendImpl.java @@ -0,0 +1,142 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.mail; + +import java.util.Date; +import java.util.Properties; + +import javax.mail.internet.MimeMessage; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.task.TaskExecutor; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.springframework.mail.javamail.MimeMessageHelper; + +import com.alibaba.fastjson2.JSON; + +import cn.topiam.employee.common.message.SendMessageException; +import cn.topiam.employee.common.message.enums.MailProvider; +import cn.topiam.employee.common.message.enums.MailSafetyType; +import cn.topiam.employee.common.message.enums.MessageType; +import static org.apache.commons.codec.CharEncoding.UTF_8; +import static org.apache.commons.lang3.BooleanUtils.TRUE; + +/** + * 默认邮件发送实现类 + * + * @author TopIAM + * Created by support@topiam.cn on 2021-10-02 21:40 + **/ +public class DefaultMailProviderSendImpl implements MailProviderSend { + + private final Logger log = LoggerFactory + .getLogger(DefaultMailProviderSendImpl.class); + public static final String MAIL_SMTP_SOCKET_FACTORY_CLASS = "mail.smtp.socketFactory.class"; + public static final String JAVAX_NET_SSL_SSLSOCKET_FACTORY = "javax.net.ssl.SSLSocketFactory"; + public static final String MAIL_SMTP_AUTH = "mail.smtp.auth"; + private final MailProviderConfig mailProvider; + + public DefaultMailProviderSendImpl(MailProviderConfig mailProvider, TaskExecutor taskExecutor) { + this.mailProvider = mailProvider; + this.javaMailSender = init(mailProvider); + this.taskExecutor = taskExecutor; + } + + /** + * init + * + * @param mailProvider {@link MailProviderConfig} + * @return {@link JavaMailSender} + */ + public JavaMailSender init(MailProviderConfig mailProvider) { + JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl(); + javaMailSender.setHost(mailProvider.getSmtpUrl()); + javaMailSender.setUsername(mailProvider.getUsername()); + javaMailSender.setPassword(mailProvider.getDecryptSecret()); + javaMailSender.setDefaultEncoding(UTF_8); + javaMailSender.setPort(mailProvider.getPort()); + if (mailProvider.getSafetyType() == MailSafetyType.SSL) { + Properties properties = new Properties(); + properties.setProperty(MAIL_SMTP_AUTH, TRUE); + properties.setProperty(MAIL_SMTP_SOCKET_FACTORY_CLASS, JAVAX_NET_SSL_SSLSOCKET_FACTORY); + javaMailSender.setJavaMailProperties(properties); + } + return javaMailSender; + } + + /** + * send + * @param sendMail {@link SendMailRequest} + */ + @Override + public void sendMail(SendMailRequest sendMail) { + send(sendMail, false); + } + + /** + * sendMailHtml + * + * @param sendMail {@link SendMailRequest} + */ + @Override + public void sendMailHtml(SendMailRequest sendMail) { + send(sendMail, true); + } + + /** + * 发送邮件 + * + * @param sendMail {@link SendMailRequest} + * @param isHtml {@link Boolean} + */ + private void send(SendMailRequest sendMail, boolean isHtml) { + log.info("发送邮件消息入参: {}", JSON.toJSONString(sendMail)); + try { + MimeMessage mimeMessage = javaMailSender.createMimeMessage(); + MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, false, UTF_8); + //设置发件人 + messageHelper.setFrom(mailProvider.getUsername(), sendMail.getSender()); + messageHelper.setTo(sendMail.getReceiver()); + messageHelper.setSubject(sendMail.getSubject()); + //设置邮件消息 + messageHelper.setText(sendMail.getBody(), isHtml); + //设置发送的日期 + messageHelper.setSentDate(new Date()); + taskExecutor.execute(() -> javaMailSender.send(mimeMessage)); + } catch (Exception e) { + log.info("发送邮件消息失败: {}", e.getMessage()); + throw new SendMessageException(MessageType.MAIL, e.getMessage()); + } + } + + @Override + public MailProvider getProvider() { + return mailProvider.getProvider(); + } + + /** + * JavaMailSender + */ + private final JavaMailSender javaMailSender; + /** + * ThreadPoolTaskExecutor + */ + private final TaskExecutor taskExecutor; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/mail/MailNoneProviderSend.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/mail/MailNoneProviderSend.java new file mode 100644 index 00000000..c5c9fa8f --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/mail/MailNoneProviderSend.java @@ -0,0 +1,44 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.mail; + +import cn.topiam.employee.common.message.enums.MailProvider; + +/** + * None + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/27 21:06 + */ +public class MailNoneProviderSend implements MailProviderSend { + + @Override + public void sendMail(SendMailRequest sendMailParam) { + + } + + @Override + public void sendMailHtml(SendMailRequest sendMailParam) { + + } + + @Override + public MailProvider getProvider() { + return null; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/mail/MailProviderConfig.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/mail/MailProviderConfig.java new file mode 100644 index 00000000..859614d8 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/mail/MailProviderConfig.java @@ -0,0 +1,91 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.mail; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import cn.topiam.employee.common.message.enums.MailProvider; +import cn.topiam.employee.common.message.enums.MailSafetyType; +import cn.topiam.employee.support.util.AesUtils; + +import lombok.Builder; +import lombok.Data; + +/** + * MailProviderConfig + * + * @author TopIAM + * Created by support@topiam.cn on 2021/10/1 19:10 + */ +@Data +@Builder +public class MailProviderConfig { + public MailProviderConfig() { + } + + public MailProviderConfig(MailProvider provider, String smtpUrl, Integer port, + MailSafetyType safetyType, String username, String secret) { + this.provider = provider; + this.smtpUrl = smtpUrl; + this.port = port; + this.safetyType = safetyType; + this.username = username; + this.secret = secret; + } + + /** + * 平台 + */ + @NotNull(message = "平台不能为空") + private MailProvider provider; + + /** + * smtp地址 + */ + @NotEmpty(message = "smtp地址不能为空") + private String smtpUrl; + + /** + * 端口 + */ + @NotNull(message = "端口号不能为空") + private Integer port; + + /** + * 安全验证 + */ + @NotNull(message = "安全验证方式不能为空") + private MailSafetyType safetyType; + + /** + * 用户名 + */ + @NotEmpty(message = "用户名不能为空") + private String username; + + /** + * 密码 + */ + @NotEmpty(message = "密码不能为空") + private String secret; + + public String getDecryptSecret() { + return AesUtils.decrypt(this.secret); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/mail/MailProviderSend.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/mail/MailProviderSend.java new file mode 100644 index 00000000..51b0b5e0 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/mail/MailProviderSend.java @@ -0,0 +1,49 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.mail; + +import cn.topiam.employee.common.message.enums.MailProvider; + +/** + * 邮件收发统一接口 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/27 21:06 + */ +public interface MailProviderSend { + + /** + * 发送普通邮件 + * + * @param sendMailParam 发送邮件的参数 + */ + void sendMail(SendMailRequest sendMailParam); + + /** + * 发送html的邮件 + * + * @param sendMailParam 发送邮件的参数 + */ + void sendMailHtml(SendMailRequest sendMailParam); + + /** + * 服务商类型 + * @return {@link MailProvider} + */ + MailProvider getProvider(); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/mail/SendMailRequest.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/mail/SendMailRequest.java new file mode 100644 index 00000000..73bc757e --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/mail/SendMailRequest.java @@ -0,0 +1,48 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.mail; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 发送邮件参数 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/27 21:08 + */ +@Data +@Accessors(chain = true) +public class SendMailRequest { + /** + * 发送人 + */ + private String sender; + /** + * 收件人 + */ + private String receiver; + /** + * 主题 + */ + private String subject; + /** + * 内容 + */ + private String body; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/mail/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/mail/package-info.java new file mode 100644 index 00000000..a2ab3e10 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/mail/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.mail; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SendSmsRequest.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SendSmsRequest.java new file mode 100644 index 00000000..94fd9af2 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SendSmsRequest.java @@ -0,0 +1,47 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.sms; + +import java.io.Serializable; +import java.util.Map; + +import lombok.Data; + +/** + * 短信发送入参 + * + * @author TopIAM + */ +@Data +public class SendSmsRequest implements Serializable { + + /** + * 手机号 + */ + private String phone; + + /** + * 模板 + */ + private String template; + + /** + * 参数 + */ + private Map parameters; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsNoneProviderSend.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsNoneProviderSend.java new file mode 100644 index 00000000..db921ba8 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsNoneProviderSend.java @@ -0,0 +1,38 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.sms; + +import cn.topiam.employee.common.message.enums.SmsProvider; + +/** + * None + * + * @author TopIAM + * Created by support@topiam.cn on 2022/4/20 23:48 + */ +public class SmsNoneProviderSend implements SmsProviderSend { + @Override + public SmsResponse send(SendSmsRequest request) { + return null; + } + + @Override + public SmsProvider getProvider() { + return null; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsProviderConfig.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsProviderConfig.java new file mode 100644 index 00000000..00ea07b9 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsProviderConfig.java @@ -0,0 +1,54 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.sms; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotNull; + +import cn.topiam.employee.common.message.enums.SmsProvider; + +/** + * 验证码提供商配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/10/1 19:10 + */ +public class SmsProviderConfig implements Serializable { + + @Serial + private static final long serialVersionUID = 5611656522133230183L; + + /** + * 平台 + */ + @NotNull(message = "平台类型不能为空") + private SmsProvider provider; + + public SmsProvider getProvider() { + return provider; + } + + public void setProvider(SmsProvider provider) { + this.provider = provider; + } + + public SmsProviderConfig() { + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsProviderSend.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsProviderSend.java new file mode 100644 index 00000000..6fddf48d --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsProviderSend.java @@ -0,0 +1,51 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.sms; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import cn.topiam.employee.common.message.enums.SmsProvider; + +/** + * 短信发送 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/27 21:03 + */ +public interface SmsProviderSend { + + Logger log = LoggerFactory.getLogger(SmsProviderSend.class); + + /** + * 发送短信验证码 + *

+ * 如果是腾讯云,params要用LinkedHashMap,保证顺序 + * + * @param request {@link SendSmsRequest} + * @return {@link SmsResponse} + */ + SmsResponse send(SendSmsRequest request); + + /** + * 服务商类型 + * + * @return {@link SmsProvider} + */ + SmsProvider getProvider(); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsResponse.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsResponse.java new file mode 100644 index 00000000..6a05edd3 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsResponse.java @@ -0,0 +1,50 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.sms; + +import java.io.Serializable; + +import cn.topiam.employee.common.message.enums.SmsProvider; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * 短信发送返回 + * + * @author TopIAM + */ +@Data +@AllArgsConstructor +public class SmsResponse implements Serializable { + + /** + * 消息 + */ + private String message; + + /** + * 是否成功 + */ + private Boolean success; + + /** + * 提供商 + */ + private SmsProvider provider; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsSendProviderFactory.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsSendProviderFactory.java new file mode 100644 index 00000000..630fdeb0 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/SmsSendProviderFactory.java @@ -0,0 +1,60 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.sms; + +import cn.topiam.employee.common.message.enums.SmsProvider; +import cn.topiam.employee.common.message.sms.aliyun.AliyunSmsProviderConfig; +import cn.topiam.employee.common.message.sms.aliyun.AliyunSmsProviderSend; +import cn.topiam.employee.common.message.sms.qiniu.QiNiuSmsProviderConfig; +import cn.topiam.employee.common.message.sms.qiniu.QiNiuSmsProviderSend; +import cn.topiam.employee.common.message.sms.tencent.TencentSmsProviderConfig; +import cn.topiam.employee.common.message.sms.tencent.TencentSmsProviderSend; + +/** + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/10 19:40 + */ +public class SmsSendProviderFactory { + + private SmsSendProviderFactory() { + } + + /** + * 获取实例化 + * + * @param config {@link SmsProviderConfig} + * @return {@link SmsProviderSend} + */ + public static SmsProviderSend newInstance(SmsProviderConfig config) { + SmsProvider provider = config.getProvider(); + //阿里云 + if (SmsProvider.ALIYUN.equals(provider)) { + return new AliyunSmsProviderSend((AliyunSmsProviderConfig) config); + } + //腾讯 + if (SmsProvider.TENCENT.equals(provider)) { + return new TencentSmsProviderSend((TencentSmsProviderConfig) config); + } + //七牛 + if (SmsProvider.QINIU.equals(provider)) { + return new QiNiuSmsProviderSend((QiNiuSmsProviderConfig) config); + } + throw new IllegalArgumentException("暂未支持该短信提供商类型"); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/aliyun/AliyunSmsProviderConfig.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/aliyun/AliyunSmsProviderConfig.java new file mode 100644 index 00000000..42add9d1 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/aliyun/AliyunSmsProviderConfig.java @@ -0,0 +1,56 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.sms.aliyun; + +import javax.validation.constraints.NotEmpty; + +import cn.topiam.employee.common.message.sms.SmsProviderConfig; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 验证码提供商配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/10/1 19:10 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class AliyunSmsProviderConfig extends SmsProviderConfig { + public AliyunSmsProviderConfig() { + } + + /** + * accessKeyId + */ + @NotEmpty(message = "accessKeyId不能为空") + private String accessKeyId; + + /** + * accessKeySecret + */ + @NotEmpty(message = "accessKeySecret不能为空") + private String accessKeySecret; + + /** + * signNme + */ + @NotEmpty(message = "signNme不能为空") + private String signName; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/aliyun/AliyunSmsProviderSend.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/aliyun/AliyunSmsProviderSend.java new file mode 100644 index 00000000..8dd42493 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/aliyun/AliyunSmsProviderSend.java @@ -0,0 +1,84 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.sms.aliyun; + +import com.alibaba.fastjson2.JSON; +import com.aliyun.dysmsapi20170525.Client; +import com.aliyun.dysmsapi20170525.models.SendSmsResponse; +import com.aliyun.tea.TeaException; +import com.aliyun.teaopenapi.models.Config; +import com.aliyun.teautil.models.RuntimeOptions; + +import cn.topiam.employee.common.message.enums.SmsProvider; +import cn.topiam.employee.common.message.sms.SendSmsRequest; +import cn.topiam.employee.common.message.sms.SmsProviderSend; +import cn.topiam.employee.common.message.sms.SmsResponse; + +/** + * 阿里云短信发送 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/4/14 23:04 + */ +@SuppressWarnings("ALL") +public class AliyunSmsProviderSend implements SmsProviderSend { + + private final AliyunSmsProviderConfig config; + private Client client; + + public AliyunSmsProviderSend(AliyunSmsProviderConfig config) { + this.config = config; + Config aliConfig = new Config(); + aliConfig.setAccessKeyId(config.getAccessKeyId()); + aliConfig.setAccessKeySecret(config.getAccessKeySecret()); + aliConfig.setEndpoint("dysmsapi.aliyuncs.com"); + try { + this.client = new Client(aliConfig); + } catch (Exception e) { + log.error("阿里云短信客戶端初始化失败: [{}]", e.getMessage()); + } + } + + @Override + public SmsResponse send(SendSmsRequest sendSmsParam) { + try { + RuntimeOptions runtimeOptions = new RuntimeOptions(); + com.aliyun.dysmsapi20170525.models.SendSmsRequest request = new com.aliyun.dysmsapi20170525.models.SendSmsRequest() + .setPhoneNumbers(sendSmsParam.getPhone()).setSignName(this.config.getSignName()) + .setTemplateCode(sendSmsParam.getTemplate()) + .setTemplateParam(JSON.toJSONString(sendSmsParam.getParameters())); + SendSmsResponse sendSmsResponse = this.client.sendSmsWithOptions(request, + runtimeOptions); + String code = sendSmsResponse.getBody().getCode(); + String message = sendSmsResponse.getBody().getMessage(); + return new SmsResponse(message, "OK".equals(code), SmsProvider.ALIYUN); + } catch (TeaException error) { + log.error("阿里云发送短信异常: [{}]", error.message); + return new SmsResponse(error.message, Boolean.FALSE, SmsProvider.ALIYUN); + } catch (Exception exception) { + TeaException error = new TeaException(exception.getMessage(), exception); + log.error("阿里云发送短信异常: [{}]", error.message); + return new SmsResponse(error.message, Boolean.FALSE, SmsProvider.ALIYUN); + } + } + + @Override + public SmsProvider getProvider() { + return SmsProvider.ALIYUN; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/aliyun/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/aliyun/package-info.java new file mode 100644 index 00000000..365b28d1 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/aliyun/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.sms.aliyun; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/package-info.java new file mode 100644 index 00000000..2b9bb7ea --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.sms; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/qiniu/QiNiuSmsProviderConfig.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/qiniu/QiNiuSmsProviderConfig.java new file mode 100644 index 00000000..16b70266 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/qiniu/QiNiuSmsProviderConfig.java @@ -0,0 +1,49 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.sms.qiniu; + +import javax.validation.constraints.NotEmpty; + +import cn.topiam.employee.common.message.sms.SmsProviderConfig; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 验证码提供商配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/10/1 19:10 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class QiNiuSmsProviderConfig extends SmsProviderConfig { + public QiNiuSmsProviderConfig() { + } + + /** + * accessKey + */ + @NotEmpty(message = "accessKey不能为空") + private String accessKey; + /** + * secretKey + */ + @NotEmpty(message = "secretKey不能为空") + private String secretKey; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/qiniu/QiNiuSmsProviderSend.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/qiniu/QiNiuSmsProviderSend.java new file mode 100644 index 00000000..415ee3e6 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/qiniu/QiNiuSmsProviderSend.java @@ -0,0 +1,69 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.sms.qiniu; + +import org.springframework.util.StringUtils; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.qiniu.common.QiniuException; +import com.qiniu.http.Response; +import com.qiniu.sms.SmsManager; +import com.qiniu.util.Auth; + +import cn.topiam.employee.common.message.enums.SmsProvider; +import cn.topiam.employee.common.message.sms.SendSmsRequest; +import cn.topiam.employee.common.message.sms.SmsProviderSend; +import cn.topiam.employee.common.message.sms.SmsResponse; + +/** + * 七牛短信发送 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/4/14 23:04 + */ +public class QiNiuSmsProviderSend implements SmsProviderSend { + private final SmsManager smsManager; + + public QiNiuSmsProviderSend(QiNiuSmsProviderConfig config) { + Auth auth = Auth.create(config.getAccessKey(), config.getSecretKey()); + // 实例化一个SmsManager对象 + this.smsManager = new SmsManager(auth); + } + + @Override + public SmsResponse send(SendSmsRequest sendSmsParam) { + try { + Response resp = smsManager.sendSingleMessage(sendSmsParam.getTemplate(), + sendSmsParam.getPhone(), sendSmsParam.getParameters()); + JSONObject response = JSON.parseObject(resp.bodyString()); + if (response.containsKey("message_id") + && StringUtils.hasText(response.getString("message_id"))) { + return new SmsResponse("成功", Boolean.TRUE, SmsProvider.QINIU); + } + } catch (QiniuException e) { + log.error("七牛发送短信异常: [{}]", e.getMessage()); + } + return new SmsResponse("失败", Boolean.FALSE, SmsProvider.QINIU); + } + + @Override + public SmsProvider getProvider() { + return SmsProvider.QINIU; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/qiniu/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/qiniu/package-info.java new file mode 100644 index 00000000..e67f664c --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/qiniu/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.sms.qiniu; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/tencent/TencentSmsProviderConfig.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/tencent/TencentSmsProviderConfig.java new file mode 100644 index 00000000..ba252db0 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/tencent/TencentSmsProviderConfig.java @@ -0,0 +1,68 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.sms.tencent; + +import javax.validation.constraints.NotEmpty; + +import cn.topiam.employee.common.message.sms.SmsProviderConfig; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 验证码提供商配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/10/1 19:10 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class TencentSmsProviderConfig extends SmsProviderConfig { + public TencentSmsProviderConfig() { + } + + /** + * secretId + */ + @NotEmpty(message = "SecretId不能为空") + private String secretId; + + /** + * secretKey + */ + @NotEmpty(message = "SecretKey不能为空") + private String secretKey; + + /** + * 短信应用ID + */ + @NotEmpty(message = "短信应用ID不能为空") + private String sdkAppId; + + /** + * 短信签名内容 + */ + @NotEmpty(message = "短信签名内容不能为空") + private String signName; + + /** + * Region + */ + @NotEmpty(message = "Region不能为空") + private String region; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/tencent/TencentSmsProviderSend.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/tencent/TencentSmsProviderSend.java new file mode 100644 index 00000000..651fc34e --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/tencent/TencentSmsProviderSend.java @@ -0,0 +1,135 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.sms.tencent; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.tencentcloudapi.common.Credential; +import com.tencentcloudapi.common.exception.TencentCloudSDKException; +import com.tencentcloudapi.sms.v20210111.SmsClient; +import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse; +import com.tencentcloudapi.sms.v20210111.models.SendStatus; + +import cn.topiam.employee.common.message.enums.SmsProvider; +import cn.topiam.employee.common.message.sms.SendSmsRequest; +import cn.topiam.employee.common.message.sms.SmsProviderSend; +import cn.topiam.employee.common.message.sms.SmsResponse; + +/** + * 腾讯短信发送 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/4/14 23:04 + */ +public class TencentSmsProviderSend implements SmsProviderSend { + private final TencentSmsProviderConfig config; + private final SmsClient client; + + public TencentSmsProviderSend(TencentSmsProviderConfig config) { + this.config = config; + /* 必要步骤: + * 实例化一个认证对象,入参需要传入腾讯云账户密钥对secretId,secretKey。 + * 这里采用的是从环境变量读取的方式,需要在环境变量中先设置这两个值。 + * 你也可以直接在代码中写死密钥对,但是小心不要将代码复制、上传或者分享给他人, + * 以免泄露密钥对危及你的财产安全。 + * SecretId、SecretKey 查询: https://console.cloud.tencent.com/cam/capi */ + Credential cred = new Credential(config.getSecretId(), config.getSecretKey()); + /* 实例化要请求产品(以sms为例)的client对象 + * 第二个参数是地域信息,可以直接填写字符串ap-guangzhou + * 支持的地域列表参考 https://cloud.tencent.com/document/api/382/52071#.E5.9C.B0.E5.9F.9F.E5.88.97.E8.A1.A8 + */ + this.client = new SmsClient(cred, config.getRegion()); + } + + @Override + public SmsResponse send(SendSmsRequest sendSmsParam) { + try { + /* 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数 + * 你可以直接查询SDK源码确定接口有哪些属性可以设置 + * 属性可能是基本类型,也可能引用了另一个数据结构 + * 推荐使用IDE进行开发,可以方便的跳转查阅各个接口和数据结构的文档说明 */ + com.tencentcloudapi.sms.v20210111.models.SendSmsRequest req = new com.tencentcloudapi.sms.v20210111.models.SendSmsRequest(); + /* 填充请求参数,这里request对象的成员变量即对应接口的入参 + * 你可以通过官网接口文档或跳转到request对象的定义处查看请求参数的定义 + * 基本类型的设置: + * 帮助链接: + * 短信控制台: https://console.cloud.tencent.com/smsv2 + * 腾讯云短信小助手: https://cloud.tencent.com/document/product/382/3773#.E6.8A.80.E6.9C.AF.E4.BA.A4.E6.B5.81 */ + + /* 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId,示例如1400006666 */ + // 应用 ID 可前往 [短信控制台](https://console.cloud.tencent.com/smsv2/app-manage) 查看 + req.setSmsSdkAppId(this.config.getSdkAppId()); + + /* 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名 */ + // 签名信息可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-sign) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-sign) 的签名管理查看 + req.setSignName(this.config.getSignName()); + + /* 模板 ID: 必须填写已审核通过的模板 ID */ + // 模板 ID 可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-template) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-template) 的正文模板管理查看 + String templateId = sendSmsParam.getTemplate(); + req.setTemplateId(templateId); + + /* 模板参数: 模板参数的个数需要与 TemplateId 对应模板的变量个数保持一致,若无模板参数,则设置为空 */ + Map parameters = sendSmsParam.getParameters(); + List templateParamList = new ArrayList<>(); + parameters.forEach((key, value) -> { + templateParamList.add(value); + }); + req.setTemplateParamSet(templateParamList.toArray(new String[0])); + /* 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号] + * 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号 */ + String phone = sendSmsParam.getPhone(); + String[] phoneNumber = { phone.startsWith("+") ? phone : "+86" + phone }; + req.setPhoneNumberSet(phoneNumber); + + /* 用户的 session 内容(无需要可忽略): 可以携带用户侧 ID 等上下文信息,server 会原样返回 */ + String sessionContext = ""; + req.setSessionContext(sessionContext); + /* 短信码号扩展号(无需要可忽略): 默认未开通,如需开通请联系 [腾讯云短信小助手] */ + String extendCode = ""; + req.setExtendCode(extendCode); + /* 国际/港澳台短信 SenderId(无需要可忽略): 国内短信填空,默认未开通,如需开通请联系 [腾讯云短信小助手] */ + String senderid = ""; + req.setSenderId(senderid); + /* 通过 client 对象调用 SendSms 方法发起请求。注意请求方法名与请求对象是对应的 + * 返回的 res 是一个 SendSmsResponse 类的实例,与请求对象对应 */ + SendSmsResponse res = client.SendSms(req); + /* 当出现以下错误码时,快速解决方案参考 + * [FailedOperation.SignatureIncorrectOrUnapproved](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Afailedoperation.signatureincorrectorunapproved-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) + * [FailedOperation.TemplateIncorrectOrUnapproved](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Afailedoperation.templateincorrectorunapproved-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) + * [UnauthorizedOperation.SmsSdkAppIdVerifyFail](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Aunauthorizedoperation.smssdkappidverifyfail-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) + * [UnsupportedOperation.ContainDomesticAndInternationalPhoneNumber](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Aunsupportedoperation.containdomesticandinternationalphonenumber-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) + * 更多错误,可咨询[腾讯云助手](https://tccc.qcloud.com/web/im/index.html#/chat?webAppId=8fa15978f85cb41f7e2ea36920cb3ae1&title=Sms) + */ + log.info(SendSmsResponse.toJsonString(res)); + SendStatus sendStatus = res.getSendStatusSet()[0]; + return new SmsResponse(sendStatus.getMessage(), "Ok".equals(sendStatus.getCode()), + SmsProvider.TENCENT); + } catch (TencentCloudSDKException e) { + log.error("腾讯云发送短信异常, code: [{}], message: [{}]", e.getErrorCode(), e.getMessage()); + return new SmsResponse(e.getMessage(), Boolean.FALSE, SmsProvider.TENCENT); + } + } + + @Override + public SmsProvider getProvider() { + return SmsProvider.TENCENT; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/tencent/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/tencent/package-info.java new file mode 100644 index 00000000..81b01266 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/message/sms/tencent/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.message.sms.tencent; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/package-info.java new file mode 100644 index 00000000..c0d73399 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/MailSendRecordRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/MailSendRecordRepository.java new file mode 100644 index 00000000..b8450180 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/MailSendRecordRepository.java @@ -0,0 +1,33 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.MailSendRecordEntity; + +/** + * MailSendRecordRepository + * + * @author TopIAM + * Created by support@topiam.cn on 2021/10/3 03:38 + */ +@Repository +public interface MailSendRecordRepository extends CrudRepository { +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/SmsSendRecordRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/SmsSendRecordRepository.java new file mode 100644 index 00000000..214be14e --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/SmsSendRecordRepository.java @@ -0,0 +1,31 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.SmsSendRecordEntity; + +/** + * @author TopIAM + */ +@Repository +public interface SmsSendRecordRepository extends CrudRepository { + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/OrganizationMemberCustomizedRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/OrganizationMemberCustomizedRepository.java new file mode 100644 index 00000000..39150e1d --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/OrganizationMemberCustomizedRepository.java @@ -0,0 +1,38 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account; + +import java.util.List; + +import cn.topiam.employee.common.entity.account.OrganizationMemberEntity; + +/** + * 组织成员 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/10/2 02:53 + */ +public interface OrganizationMemberCustomizedRepository { + + /** + * 批量保存 + * + * @param list {@link String} + */ + void batchSave(List list); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/OrganizationMemberRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/OrganizationMemberRepository.java new file mode 100644 index 00000000..a6f0be7a --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/OrganizationMemberRepository.java @@ -0,0 +1,77 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.common.entity.account.OrganizationMemberEntity; + +/** + * 组织机构成员 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/30 03:06 + */ +@Repository +public interface OrganizationMemberRepository extends JpaRepository, + QuerydslPredicateExecutor, + OrganizationMemberCustomizedRepository { + + /** + * 根据组织机构ID和用户ID删除 + * + * @param orgId {@link Long} + * @param userId {@link Long} + */ + @Transactional(rollbackFor = Exception.class) + void deleteByOrgIdAndUserId(@Param("orgId") String orgId, @Param("userId") Long userId); + + /** + * 根据根据用户id查询关联的组织 + * + * @param userId {@link Long} + * @return {@link List} + */ + List findAllByUserId(@Param("userId") Long userId); + + /** + * 根据用户ID 批量删除关联关系 + * + * @param userIds {@link String} + */ + @Modifying + @Query(value = "DELETE FROM organization_member WHERE user_id IN :userIds", nativeQuery = true) + void deleteAllByUserId(@Param("userIds") Iterable userIds); + + /** + * 根据用户ID 删除关联关系 + * + * @param id {@link String} + */ + @Modifying + @Query(value = "DELETE FROM organization_member WHERE user_id = :id", nativeQuery = true) + void deleteByUserId(@Param("id") Long id); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/OrganizationRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/OrganizationRepository.java new file mode 100644 index 00000000..2b488abf --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/OrganizationRepository.java @@ -0,0 +1,200 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.common.entity.account.OrganizationEntity; +import cn.topiam.employee.common.enums.DataOrigin; + +/** + *

+ * 组织架构 Repository 接口 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-09 + */ +@Repository +public interface OrganizationRepository extends CrudRepository, + PagingAndSortingRepository, + JpaSpecificationExecutor, + QuerydslPredicateExecutor, + OrganizationRepositoryCustomized { + + /** + * 根据名称查询数量 + * + * @param s {@link String} + * @return {@link Long} + */ + Long countByName(String s); + + /** + * 根据名称查询组织机构 + * + * @param name {@link String} + * @return {@link OrganizationEntity} + */ + OrganizationEntity findByName(String name); + + /** + * 查询子组织 + * + * @param parentId {@link String} + * @return {@link OrganizationEntity} + */ + List findByParentId(String parentId); + + /** + * 查询子组织根据sort排序 + * + * @param parentId {@link String} + * @return {@link OrganizationEntity} + */ + List findByParentIdOrderByOrderAsc(String parentId); + + /** + * 移动组织机构 + * + * @param id {@link String} + * @param parentId {@link String} + */ + @Transactional(rollbackFor = Exception.class) + @Modifying + @Query(value = "UPDATE OrganizationEntity SET parentId =:parentId WHERE id =:id") + void moveOrganization(@Param(value = "id") String id, + @Param(value = "parentId") String parentId); + + /** + * 更新叶子接点 + * + * @param id {@link String} + * @param isLeaf {@link Boolean} + */ + @Transactional(rollbackFor = Exception.class) + @Modifying + @Query(value = "UPDATE OrganizationEntity set leaf =:isLeaf WHERE id =:id") + void updateIsLeaf(@Param(value = "id") String id, @Param(value = "isLeaf") Boolean isLeaf); + + /** + * 更新启用/禁用 + * + * @param id {@link Serializable} + * @param status {@link Boolean} + * @return {@link Integer} + */ + @Modifying + @Query(value = "UPDATE OrganizationEntity set enabled =:status WHERE id =:id") + Integer updateStatus(@Param(value = "id") String id, @Param(value = "status") Boolean status); + + /** + * 根据名称或编码查询组织机构 + * + * @param keyWord {@link String} + * @return {@link OrganizationEntity} + */ + @Query(value = "FROM OrganizationEntity WHERE name LIKE %:keyWord% OR code LIKE %:keyWord%") + List findByNameLikeOrCodeLike(@Param(value = "keyWord") String keyWord); + + /** + * 查询指定id返回组织机构 + * + * @param id {@link Collection} + * @return {@link OrganizationEntity} + */ + List findByIdInOrderByCreateTimeDesc(Collection id); + + /** + * 根据外部用id查询组织 + * + * @param deptIdList {@link String} + * @return {@link OrganizationEntity} + */ + List findByExternalIdIn(List deptIdList); + + /** + * 根据外部用id查询组织 + * + * @param deptId {@link String} + * @return {@link OrganizationEntity} + */ + Optional findByExternalId(String deptId); + + /** + * 根据外部用id查询组织 + * + * @param externalId {@link String} + * @param identitySourceId {@link Long} + * @return {@link OrganizationEntity} + */ + OrganizationEntity findByExternalIdAndIdentitySourceId(String externalId, + Long identitySourceId); + + /** + * 根据外部用id查询组织 + * + * @param externalId {@link String} + * @param identitySourceId {@link Long} + * @return {@link List} + */ + List findByExternalIdInAndIdentitySourceId(List externalId, + Long identitySourceId); + + /** + * 查询子组织 + * + * @param parentId {@link String} + * @param dataOrigin {@link DataOrigin} + * @param identitySourceId {@link String} + * @return {@link OrganizationEntity} + */ + List findByParentIdAndDataOriginAndIdentitySourceId(String parentId, + DataOrigin dataOrigin, + Long identitySourceId); + + /** + * 根据身份源ID获取所有数据 + * + * @param identitySourceId {@link Long} + * @return {@link List} + */ + List findByIdentitySourceId(Long identitySourceId); + + /** + * 通过parentId查询 + * + * @param parentIds {@link List} + * @return {@link List} + */ + List findByIdInOrderByOrderAsc(Collection parentIds); + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/OrganizationRepositoryCustomized.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/OrganizationRepositoryCustomized.java new file mode 100644 index 00000000..eed6a7c6 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/OrganizationRepositoryCustomized.java @@ -0,0 +1,43 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account; + +import java.util.List; + +import cn.topiam.employee.common.entity.account.OrganizationEntity; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/2/13 21:27 + */ +public interface OrganizationRepositoryCustomized { + + /** + * 批量保存 + * + * @param list {@link List} + */ + void batchSave(List list); + + /** + * 批量更新 + * + * @param list {@link List} + */ + void batchUpdate(List list); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserDetailRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserDetailRepository.java new file mode 100644 index 00000000..f3574304 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserDetailRepository.java @@ -0,0 +1,79 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.account.UserDetailEntity; + +/** + *

+ * 用户详情表 Repository 接口 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-07 + */ +@Repository +public interface UserDetailRepository extends PagingAndSortingRepository, + CrudRepository, + QuerydslPredicateExecutor, + UserDetailRepositoryCustomized { + /** + * 根据user id查询用户详情 + * + * @param user {@link String} + * @return {@link UserDetailEntity} + */ + Optional findByUserId(Long user); + + /** + * 根据用户ID删除用户 + * + * @param userId {@link Long} + */ + void deleteByUserId(Long userId); + + /** + * 根据用户ID批量删除用户 + * + * @param userIds {@link String} + */ + @Modifying + @Query(value = "DELETE FROM UserDetailEntity WHERE userId IN (:userIds)") + void deleteAllByUserId(@Param("userIds") Iterable userIds); + + /** + * 根据用户ID查询用户详情 + * + * @param userIds {@link List} + * @return {@link List} + */ + @Modifying + @Query(value = "SELECT * FROM user_detail WHERE user_detail.user_id IN :userIds", nativeQuery = true) + List findAllByUserIds(@Param("userIds") Iterable userIds); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserDetailRepositoryCustomized.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserDetailRepositoryCustomized.java new file mode 100644 index 00000000..ed552fe3 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserDetailRepositoryCustomized.java @@ -0,0 +1,45 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account; + +import java.util.ArrayList; +import java.util.List; + +import cn.topiam.employee.common.entity.account.UserDetailEntity; + +/** + * User Detail Repository Customized + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/29 20:27 + */ +public interface UserDetailRepositoryCustomized { + /** + * 批量新增 + * + * @param list {@link List} + */ + void batchSave(List list); + + /** + * 批量更新 + * + * @param list {@link List} + */ + void batchUpdate(ArrayList list); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserGroupMemberRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserGroupMemberRepository.java new file mode 100644 index 00000000..3782d4cf --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserGroupMemberRepository.java @@ -0,0 +1,78 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.common.entity.account.UserGroupMemberEntity; + +/** + * 用户组成员 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/30 03:04 + */ +@Repository +public interface UserGroupMemberRepository extends JpaRepository, + QuerydslPredicateExecutor, + UserGroupMemberRepositoryCustomized { + + /** + * 根据用户组ID和用户ID删除 + * + * @param groupId {@link String} + * @param userId {@link String} + */ + @Transactional(rollbackFor = Exception.class) + void deleteByGroupIdAndUserId(@Param("groupId") Long groupId, @Param("userId") Long userId); + + /** + * 根据用户id所有用户组关联信息 + * + * @param userId {@link Long} + * + * @return {@link List} + */ + List findByUserId(@Param("userId") Long userId); + + /** + * 根据用户ID 批量删除关联关系 + * + * @param userIds {@link String} + */ + @Modifying + @Query(value = "DELETE FROM user_group_member WHERE user_id IN :userIds", nativeQuery = true) + void deleteAllByUserId(@Param("userIds") Iterable userIds); + + /** + * 根据用户ID 删除关联关系 + * + * @param id {@link Long} + */ + @Modifying + @Query(value = "DELETE FROM user_group_member WHERE user_id = :id", nativeQuery = true) + void deleteByUserId(@Param("id") Long id); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserGroupMemberRepositoryCustomized.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserGroupMemberRepositoryCustomized.java new file mode 100644 index 00000000..676d0238 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserGroupMemberRepositoryCustomized.java @@ -0,0 +1,39 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import cn.topiam.employee.common.entity.account.po.UserPO; +import cn.topiam.employee.common.entity.account.query.UserGroupMemberListQuery; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/2/13 21:27 + */ +public interface UserGroupMemberRepositoryCustomized { + /** + * 获取用户组成员列表 + * + * @param query {@link UserGroupMemberListQuery} + * @param pageable {@link Pageable} + * @return {@link Page} + */ + Page getUserGroupMemberList(UserGroupMemberListQuery query, Pageable pageable); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserGroupRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserGroupRepository.java new file mode 100644 index 00000000..f1559baf --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserGroupRepository.java @@ -0,0 +1,40 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account; + +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.account.UserGroupEntity; + +/** + *

+ * 用户组表 Repository 接口 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-07-31 + */ +@Repository +public interface UserGroupRepository extends CrudRepository, + PagingAndSortingRepository, + QuerydslPredicateExecutor { + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserHistoryPasswordRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserHistoryPasswordRepository.java new file mode 100644 index 00000000..6e7bda28 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserHistoryPasswordRepository.java @@ -0,0 +1,49 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account; + +import java.util.List; + +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.account.UserHistoryPasswordEntity; + +/** + *

+ * 用户历史密码表 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-07-31 + */ +@Repository +public interface UserHistoryPasswordRepository extends + CrudRepository, + PagingAndSortingRepository, + QuerydslPredicateExecutor { + /** + * 根据用户ID查询历史密码 + * + * @param userId {@link String} 用户ID + * @return {@link List } + */ + List findByUserId(String userId); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserIdpRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserIdpRepository.java new file mode 100644 index 00000000..7b8e1dfa --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserIdpRepository.java @@ -0,0 +1,37 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account; + +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.account.UserIdpBindEntity; + +/** + * 用户身份绑定表 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/4/3 22:18 + */ +@Repository +public interface UserIdpRepository extends CrudRepository, + QuerydslPredicateExecutor, + UserIdpRepositoryCustomized { + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserIdpRepositoryCustomized.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserIdpRepositoryCustomized.java new file mode 100644 index 00000000..2e82cb98 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserIdpRepositoryCustomized.java @@ -0,0 +1,58 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account; + +import java.util.Optional; + +import cn.topiam.employee.common.entity.account.po.UserIdpBindPo; +import cn.topiam.employee.support.repository.page.domain.Page; + +/** + * UserIdp Repository Customized + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/29 20:27 + */ +public interface UserIdpRepositoryCustomized { + + /** + * 根据身份源ID和openId查询 + * + * @param idpId {@link String} + * @param openId {@link String} + * @return {@link Optional} + */ + Optional findByIdpIdAndOpenId(String idpId, String openId); + + /** + * 根据身份源ID和userId查询 + * + * @param idpId {@link String} + * @param userId {@link String} + * @return {@link Optional} + */ + Optional findByIdpIdAndUserId(String idpId, Long userId); + + /** + * 查询用户身份提供商绑定 + * + * @param userId {@link Long} + * @return {@link Page} + */ + Iterable getUserIdpBindList(Long userId); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserRepository.java new file mode 100644 index 00000000..f30102c5 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserRepository.java @@ -0,0 +1,271 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import org.jetbrains.annotations.NotNull; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.common.entity.account.UserEntity; +import cn.topiam.employee.common.enums.DataOrigin; +import cn.topiam.employee.common.enums.UserStatus; +import static cn.topiam.employee.common.constants.AccountConstants.USER_CACHE_NAME; + +/** + *

+ * 用户表 Repository 接口 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-07-31 + */ +@Repository +@CacheConfig(cacheNames = { USER_CACHE_NAME }) +public interface UserRepository extends CrudRepository, + PagingAndSortingRepository, + QuerydslPredicateExecutor, UserRepositoryCustomized { + /** + * findById + * + * @param id must not be {@literal null}. + * @return {@link UserEntity} + */ + @NotNull + @Override + @Cacheable(key = "#p0", unless = "#result==null") + Optional findById(@NotNull Long id); + + /** + * findById + * + * @param id must not be {@literal null}. + */ + @Override + @CacheEvict(allEntries = true) + void deleteById(@NotNull Long id); + + /** + * findById + * + * @param ids must not be {@literal null}. + */ + @Override + @CacheEvict(allEntries = true) + void deleteAllById(@NotNull Iterable ids); + + /** + * save + * + * @param entity must not be {@literal null}. + * @return {@link UserEntity} + * @param {@link UserEntity} + */ + @Override + @CacheEvict(allEntries = true) + S save(@NotNull S entity); + + /** + * 根据用户名查询用户信息 + * + * @param username {@link String} + * @return {@link UserEntity} + */ + UserEntity findByUsername(String username); + + /** + * 根据手机号查询用户信息 + * + * @param phone {@link String} + * @return {@link UserEntity} + */ + UserEntity findByPhone(String phone); + + /** + * 根据邮件查询用户信息 + * + * @param username {@link String} + * @return {@link UserEntity} + */ + UserEntity findByEmail(String username); + + /** + * 根据扩展ID查询用户信息 + * + * @param id {@link String} + * @return {@link UserEntity} + */ + Optional findByExternalId(String id); + + /** + * 根据扩展ID查询用户信息 + * + * @param ids {@link Collection} + * @return {@link UserEntity} + */ + List findByExternalIdIn(Collection ids); + + /** + * 更新用户密码 + * + * @param id {@link Long} + * @param password {@link String} + * @param lastUpdatePasswordTime {@link LocalDateTime} + * @return {@link Integer} + */ + @Transactional(rollbackFor = Exception.class) + @Modifying + @CacheEvict(allEntries = true) + @Query(value = "update UserEntity set password =:password,lastUpdatePasswordTime = :lastUpdatePasswordTime where id=:id") + Integer updateUserPassword(@Param(value = "id") Long id, + @Param(value = "password") String password, + @Param(value = "lastUpdatePasswordTime") LocalDateTime lastUpdatePasswordTime); + + /** + * 更新用户邮箱 + * + * @param id {@link Long} + * @param email {@link String} + * @return {@link Integer} + */ + @Transactional(rollbackFor = Exception.class) + @Modifying + @CacheEvict(allEntries = true) + @Query(value = "update UserEntity set email =:email where id=:id") + Integer updateUserEmail(@Param(value = "id") Long id, @Param(value = "email") String email); + + /** + * 更新用户手机号 + * + * @param id {@link Long} + * @param phone {@link String} + * @return {@link Integer} + */ + @Transactional(rollbackFor = Exception.class) + @Modifying + @CacheEvict(allEntries = true) + @Query(value = "update UserEntity set phone =:phone where id=:id") + Integer updateUserPhone(@Param(value = "id") Long id, @Param(value = "phone") String phone); + + /** + * 更新用户状态 + * + * @param id {@link Long} + * @param status {@link UserStatus} + * @return {@link Integer} + */ + @Transactional(rollbackFor = Exception.class) + @Modifying + @CacheEvict(allEntries = true) + @Query(value = "update UserEntity set status=:status where id=:id") + Integer updateUserStatus(@Param(value = "id") Long id, + @Param(value = "status") UserStatus status); + + /** + * 查找密码过期警告用户 + * + * @param expireWarnDays {@link Integer} 即将到期日期 + * @return {@link UserEntity} + */ + @Query(value = "SELECT * FROM `user` WHERE DATE_ADD(DATE_FORMAT(last_update_password_time,'%Y-%m-%d'), INTERVAL :expireWarnDays DAY ) <= CURDATE() and user.status_ != 'locked'", nativeQuery = true) + List findPasswordExpireWarnUser(@Param(value = "expireWarnDays") Integer expireWarnDays); + + /** + * 查询密码已过期用户 + * + * @param expireDays {@link Integer} 密码过期日期 + * @return {@link UserEntity} + */ + @Query(value = "SELECT * FROM `user` WHERE DATE_ADD(DATE_FORMAT(last_update_password_time,'%Y-%m-%d'), INTERVAL :expireDays DAY ) BETWEEN DATE_FORMAT(DATE_SUB(NOW(),INTERVAL 1 HOUR),'%Y-%m-%d %h') AND DATE_FORMAT(DATE_SUB(NOW(),INTERVAL 1 HOUR),'%Y-%m-%d %h') and user.status_ != 'password_expired_locked'", nativeQuery = true) + List findPasswordExpireUser(@Param(value = "expireDays") Integer expireDays); + + /** + * 查询已到期用户 + * + * @return {@link UserEntity} + */ + @Query(value = "SELECT * from `user` WHERE expire_date <= CURDATE() and status_ != 'expired_locked'", nativeQuery = true) + List findExpireUser(); + + /** + * 更新用户共享密钥 + * + * @param id {@link Long} + * @param sharedSecret {@link String} + * @param totpBind {@link Boolean} + * @return {@link Integer} + */ + @CacheEvict(allEntries = true) + @Modifying + @Query(value = "update UserEntity set sharedSecret=:sharedSecret, totpBind=:totpBind where id=:id") + Integer updateUserSharedSecretAndTotpBind(@Param(value = "id") Long id, + @Param(value = "sharedSecret") String sharedSecret, + @Param(value = "totpBind") Boolean totpBind); + + /** + * 根据第三方扩展ID 删除用户 + * + * @param externalIds {@link List} + */ + void deleteAllByExternalIdIn(Collection externalIds); + + /** + * 根据用户名查询全部 + * + * @param usernames {@link String} + * @return {@link List} + */ + List findAllByUsernameIn(@Param("usernames") Collection usernames); + + /** + * 根据手机号查询全部 + * + * @param phones {@link String} + * @return {@link List} + */ + List findAllByPhoneIn(@Param("phones") Collection phones); + + /** + * 根据email模糊查询 + * + * @param emails {@link String} + * @return {@link List} + */ + List findAllByEmailIn(@Param("emails") Collection emails); + + /** + * 按Id查找不在Id中 + * @param ids {@link Collection} + * @param dataOrigin {@link DataOrigin} + * @return {@link List} + */ + List findAllByIdNotInAndDataOrigin(Collection ids, DataOrigin dataOrigin); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserRepositoryCustomized.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserRepositoryCustomized.java new file mode 100644 index 00000000..bebcbd3e --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/UserRepositoryCustomized.java @@ -0,0 +1,105 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account; + +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import cn.topiam.employee.common.entity.account.UserEntity; +import cn.topiam.employee.common.entity.account.po.UserPO; +import cn.topiam.employee.common.entity.account.query.UserListNotInGroupQuery; +import cn.topiam.employee.common.entity.account.query.UserListQuery; + +/** + * User Repository Customized + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/29 20:27 + */ +public interface UserRepositoryCustomized { + /** + * 获取用户列表 + * + * @param pageable {@link Pageable} + * @param query {@link UserListQuery} + * @return {@link Page} + */ + Page getUserList(UserListQuery query, Pageable pageable); + + /** + * 获取用户组成员列表 + * + * @param query {@link UserListNotInGroupQuery} + * @param pageable {@link Pageable} + * @return {@link Page} + */ + Page getUserListNotInGroupId(UserListNotInGroupQuery query, Pageable pageable); + + /** + * 根据组织ID查询用户列表 + * + * @param organizationId {@link String} + * @return {@link List} + */ + List findAllByOrgId(String organizationId); + + /** + * 根据组织ID、数据来源查询用户列表 + * + * @param organizationId {@link String} + * @param identitySourceId {@link Long} + * @return {@link List} + */ + List findAllByOrgIdAndIdentitySourceId(String organizationId, + Long identitySourceId); + + /** + * 按组织外部 ID 和数据来源查找用户列表 + * + * @param externalId {@link String} + * @param identitySourceId {@link Long} + * @return {@link List} + */ + List findAllByOrgExternalIdAndIdentitySourceId(String externalId, + Long identitySourceId); + + /** + * 不在组织下和数据来源查找用户列表 + * + * @param identitySourceId {@link Long} + * @return {@link List} + */ + List findAllByOrgIdNotExistAndIdentitySourceId(Long identitySourceId); + + /** + * 批量新增 + * + * @param list {@link List} + */ + void batchSave(List list); + + /** + * 批量更新 + * + * @param list {@link List} + */ + void batchUpdate(List list); + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/OrganizationMemberCustomizedRepositoryImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/OrganizationMemberCustomizedRepositoryImpl.java new file mode 100644 index 00000000..a9bc5571 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/OrganizationMemberCustomizedRepositoryImpl.java @@ -0,0 +1,73 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account.impl; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.List; + +import org.jetbrains.annotations.NotNull; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.account.OrganizationMemberEntity; +import cn.topiam.employee.common.repository.account.OrganizationMemberCustomizedRepository; + +/** + * + * @author TopIAM + * Created by support@topiam.cn on 2022/10/2 02:54 + */ +@Repository +public class OrganizationMemberCustomizedRepositoryImpl implements + OrganizationMemberCustomizedRepository { + + @Override + public void batchSave(List list) { + jdbcTemplate.batchUpdate( + "INSERT INTO organization_member (id_, org_id, user_id,create_by,create_time,update_by,update_time,remark_) VALUES (?,?,?,?,?,?,?,?)", + new BatchPreparedStatementSetter() { + + @Override + public void setValues(@NotNull PreparedStatement ps, int i) throws SQLException { + OrganizationMemberEntity entity = list.get(i); + ps.setLong(1, entity.getId()); + ps.setString(2, entity.getOrgId()); + ps.setLong(3, entity.getUserId()); + ps.setString(4, entity.getCreateBy()); + ps.setTimestamp(5, Timestamp.valueOf(entity.getCreateTime())); + ps.setString(6, entity.getUpdateBy()); + ps.setTimestamp(7, Timestamp.valueOf(entity.getUpdateTime())); + ps.setString(8, entity.getRemark()); + } + + @Override + public int getBatchSize() { + return list.size(); + } + }); + } + + private final JdbcTemplate jdbcTemplate; + + public OrganizationMemberCustomizedRepositoryImpl(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/OrganizationRepositoryCustomizedImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/OrganizationRepositoryCustomizedImpl.java new file mode 100644 index 00000000..8e80e25b --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/OrganizationRepositoryCustomizedImpl.java @@ -0,0 +1,128 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account.impl; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.List; +import java.util.Objects; + +import org.jetbrains.annotations.NotNull; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.account.OrganizationEntity; +import cn.topiam.employee.common.repository.account.OrganizationRepositoryCustomized; + +/** + * + * @author TopIAM + * Created by support@topiam.cn on 2022/9/24 23:12 + */ +@Repository +public class OrganizationRepositoryCustomizedImpl implements OrganizationRepositoryCustomized { + + /** + * 批量保存 + * + * @param list {@link List} + */ + @Override + public void batchSave(List list) { + jdbcTemplate.batchUpdate( + "INSERT INTO organization (id_, code_, name_, parent_id, is_leaf, external_id, data_origin, type_, is_enabled, order_, path_, display_path, identity_source_id,create_by,create_time,update_by,update_time,remark_) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", + new BatchPreparedStatementSetter() { + @Override + public void setValues(@NotNull PreparedStatement ps, int i) throws SQLException { + OrganizationEntity entity = list.get(i); + ps.setString(1, entity.getId()); + ps.setString(2, entity.getCode()); + ps.setString(3, entity.getName()); + ps.setString(4, entity.getParentId()); + ps.setBoolean(5, entity.getLeaf()); + ps.setString(6, entity.getExternalId()); + ps.setString(7, entity.getDataOrigin().getCode()); + ps.setString(8, entity.getType().getCode()); + ps.setBoolean(9, entity.getEnabled()); + ps.setObject(10, !Objects.isNull(entity.getOrder()) ? entity.getOrder() : null); + ps.setString(11, entity.getPath()); + ps.setString(12, entity.getDisplayPath()); + ps.setLong(13, entity.getIdentitySourceId()); + ps.setString(14, entity.getCreateBy()); + ps.setTimestamp(15, Timestamp.valueOf(entity.getCreateTime())); + ps.setString(16, entity.getUpdateBy()); + ps.setTimestamp(17, Timestamp.valueOf(entity.getUpdateTime())); + ps.setString(18, entity.getRemark()); + } + + @Override + public int getBatchSize() { + return list.size(); + } + }); + } + + /** + * 批量更新 + * + * @param list {@link List} + */ + @Override + public void batchUpdate(List list) { + jdbcTemplate.batchUpdate( + "UPDATE organization SET code_=?, name_=?, parent_id=?, is_leaf=?, external_id=?, data_origin=?, type_=? ,is_enabled=?,order_=?, path_=?,display_path=?,identity_source_id=?,create_by=?,create_time=?,update_by=?,update_time=?,remark_=? WHERE id_=?", + new BatchPreparedStatementSetter() { + + @Override + public void setValues(@NotNull PreparedStatement ps, int i) throws SQLException { + OrganizationEntity entity = list.get(i); + ps.setString(1, entity.getCode()); + ps.setString(2, entity.getName()); + ps.setString(3, entity.getParentId()); + ps.setBoolean(4, entity.getLeaf()); + ps.setString(5, entity.getExternalId()); + ps.setString(6, entity.getDataOrigin().getCode()); + ps.setString(7, entity.getType().getCode()); + ps.setBoolean(8, entity.getEnabled()); + ps.setObject(9, Objects.isNull(entity.getOrder()) ? null : entity.getOrder()); + ps.setString(10, entity.getPath()); + ps.setString(11, entity.getDisplayPath()); + ps.setLong(12, entity.getIdentitySourceId()); + ps.setString(13, entity.getCreateBy()); + ps.setTimestamp(14, Timestamp.valueOf(entity.getCreateTime())); + ps.setString(15, entity.getUpdateBy()); + ps.setTimestamp(16, Timestamp.valueOf(entity.getUpdateTime())); + ps.setString(17, entity.getRemark()); + ps.setString(18, entity.getId()); + } + + @Override + public int getBatchSize() { + return list.size(); + } + }); + } + + private final JdbcTemplate jdbcTemplate; + + public OrganizationRepositoryCustomizedImpl(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/UserDetailRepositoryCustomizedImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/UserDetailRepositoryCustomizedImpl.java new file mode 100644 index 00000000..677a956d --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/UserDetailRepositoryCustomizedImpl.java @@ -0,0 +1,120 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account.impl; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.jetbrains.annotations.NotNull; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.account.UserDetailEntity; +import cn.topiam.employee.common.repository.account.UserDetailRepositoryCustomized; + +/** + * User Detail Repository Customized + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/29 20:27 + */ +@Repository +public class UserDetailRepositoryCustomizedImpl implements UserDetailRepositoryCustomized { + + /** + * 批量更新或新增 + * + * @param data {@link List} + */ + @Override + public void batchSave(List data) { + jdbcTemplate.batchUpdate( + "INSERT INTO user_detail (id_, user_id, id_type, id_card, website_,address_,create_by,create_time,update_by,update_time,remark_) values (?,?,?,?,?,?,?,?,?,?,?)", + new BatchPreparedStatementSetter() { + + @Override + public void setValues(@NotNull PreparedStatement ps, int i) throws SQLException { + UserDetailEntity entity = data.get(i); + ps.setLong(1, entity.getId()); + ps.setLong(2, entity.getUserId()); + ps.setString(3, + Objects.isNull(entity.getIdType()) ? null : entity.getIdType().getCode()); + ps.setString(4, entity.getIdCard()); + ps.setString(5, entity.getWebsite()); + ps.setString(6, entity.getAddress()); + ps.setString(7, entity.getCreateBy()); + ps.setTimestamp(8, Timestamp.valueOf(entity.getCreateTime())); + ps.setString(9, entity.getUpdateBy()); + ps.setTimestamp(10, Timestamp.valueOf(entity.getUpdateTime())); + ps.setString(11, entity.getRemark()); + } + + @Override + public int getBatchSize() { + return data.size(); + } + }); + } + + /** + * 批量更新 + * + * @param list {@link List} + */ + @Override + public void batchUpdate(ArrayList list) { + jdbcTemplate.batchUpdate( + "UPDATE user_detail SET user_id=?,id_type=?, id_card=?, website_=? ,address_=?,create_by=?,create_time=?,update_by=?,update_time=?,remark_=? WHERE id_=?", + new BatchPreparedStatementSetter() { + + @Override + public void setValues(@NotNull PreparedStatement ps, int i) throws SQLException { + UserDetailEntity entity = list.get(i); + ps.setLong(1, entity.getUserId()); + ps.setString(2, + Objects.isNull(entity.getIdType()) ? null : entity.getIdType().getCode()); + ps.setString(3, entity.getIdCard()); + ps.setString(4, entity.getWebsite()); + ps.setString(5, entity.getAddress()); + ps.setString(6, entity.getCreateBy()); + ps.setTimestamp(7, Timestamp.valueOf(entity.getCreateTime())); + ps.setString(8, entity.getUpdateBy()); + ps.setTimestamp(9, Timestamp.valueOf(entity.getUpdateTime())); + ps.setString(10, entity.getRemark()); + ps.setLong(11, entity.getId()); + } + + @Override + public int getBatchSize() { + return list.size(); + } + }); + } + + private final JdbcTemplate jdbcTemplate; + + public UserDetailRepositoryCustomizedImpl(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/UserGroupMemberRepositoryCustomizedImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/UserGroupMemberRepositoryCustomizedImpl.java new file mode 100644 index 00000000..e5200266 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/UserGroupMemberRepositoryCustomizedImpl.java @@ -0,0 +1,113 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account.impl; + +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.account.po.UserPO; +import cn.topiam.employee.common.entity.account.query.UserGroupMemberListQuery; +import cn.topiam.employee.common.repository.account.UserGroupMemberRepositoryCustomized; +import cn.topiam.employee.common.repository.account.impl.mapper.UserPoMapper; + +import lombok.AllArgsConstructor; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/2/13 21:27 + */ +@Repository +@AllArgsConstructor +public class UserGroupMemberRepositoryCustomizedImpl implements + UserGroupMemberRepositoryCustomized { + /** + * 获取用户组成员列表 + * + * @param query {@link UserGroupMemberListQuery} + * @param pageable {@link Pageable} + * @return {@link Page} + */ + @SuppressWarnings("DuplicatedCode") + @Override + public Page getUserGroupMemberList(UserGroupMemberListQuery query, Pageable pageable) { + //@formatter:off + StringBuilder builder = new StringBuilder(""" + SELECT + `u`.id_, + `u`.username_, + `u`.password_, + `u`.email_, + `u`.phone_, + `u`.phone_area_code, + `u`.full_name, + `u`.nick_name, + `u`.avatar_, + `u`.status_, + `u`.data_origin, + `u`.email_verified, + `u`.phone_verified, + `u`.shared_secret, + `u`.totp_bind, + `u`.auth_total, + `u`.last_auth_ip, + `u`.last_auth_time, + `u`.expand_, + `u`.external_id, + `u`.expire_date, + `u`.create_by, + `u`.create_time, + `u`.update_by, + `u`.update_time, + `u`.remark_, + group_concat( organization_.display_path ) AS org_display_path + FROM + user_group_member ugm + INNER JOIN user u ON ugm.user_id = u.id_ + INNER JOIN user_group ug ON ug.id_ = ugm.group_id + LEFT JOIN organization_member ON ( u.id_ = organization_member.user_id ) + LEFT JOIN organization organization_ ON ( organization_.id_ = organization_member.org_id ) + WHERE + ugm.group_id = '%s' + AND ug.id_ = '%s' + """.formatted(query.getId(), query.getId())); + //用户名 + if (StringUtils.isNoneBlank(query.getUsername())) { + builder.append(" AND username_='%").append(query.getUsername()).append("%'"); + } + builder.append("GROUP BY `u`.id_"); + //@formatter:on + String sql = builder.toString(); + List list = jdbcTemplate.query( + builder.append(" LIMIT ").append(pageable.getPageNumber() * pageable.getPageSize()) + .append(",").append(pageable.getPageSize()).toString(), + new UserPoMapper()); + //@formatter:off + String countSql = "SELECT count(*) FROM (" + sql + ") user_"; + //@formatter:on + Integer count = jdbcTemplate.queryForObject(countSql, Integer.class); + return new PageImpl<>(list, pageable, count); + } + + private final JdbcTemplate jdbcTemplate; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/UserIdpRepositoryCustomizedImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/UserIdpRepositoryCustomizedImpl.java new file mode 100644 index 00000000..dc339a0a --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/UserIdpRepositoryCustomizedImpl.java @@ -0,0 +1,120 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account.impl; + +import java.util.Objects; +import java.util.Optional; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.account.po.UserIdpBindPo; +import cn.topiam.employee.common.repository.account.UserIdpRepositoryCustomized; +import cn.topiam.employee.common.repository.account.impl.mapper.UserIdpBindPoMapper; +import cn.topiam.employee.support.repository.page.domain.Page; + +import lombok.AllArgsConstructor; +import static cn.topiam.employee.common.constants.AccountConstants.USER_CACHE_NAME; + +/** + * UserIdp Repository Customized + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/29 20:27 + */ +@Repository +@CacheConfig(cacheNames = { USER_CACHE_NAME }) +@AllArgsConstructor +public class UserIdpRepositoryCustomizedImpl implements UserIdpRepositoryCustomized { + + /** + * 根据身份源ID和openId查询 + * + * @param idpId {@link String} + * @param openId {@link String} + * @return {@link Optional} + */ + @Override + public Optional findByIdpIdAndOpenId(String idpId, String openId) { + //@formatter:off + StringBuilder builder = new StringBuilder("SELECT uidp.*,`user`.username_,idp.name_ as idp_name FROM user_idp_bind uidp LEFT JOIN `user` ON uidp.user_id = `user`.id_ LEFT JOIN identity_provider idp ON uidp.idp_id = idp.id_ WHERE 1=1"); + //身份提供商ID + if (StringUtils.isNoneBlank(idpId)) { + builder.append(" AND uidp.idp_id = '").append(idpId).append("'"); + } + //OPEN ID + if (StringUtils.isNoneBlank(openId)) { + builder.append(" AND uidp.open_id = '").append(openId).append("'"); + } + //@formatter:on + String sql = builder.toString(); + UserIdpBindPo userIdpBindPo = jdbcTemplate.queryForObject(sql, new UserIdpBindPoMapper()); + return Optional.ofNullable(userIdpBindPo); + } + + /** + * 根据身份源ID和userId查询 + * + * @param idpId {@link String} + * @param userId {@link String} + * @return {@link Optional} + */ + @Override + public Optional findByIdpIdAndUserId(String idpId, Long userId) { + //@formatter:off + StringBuilder builder = new StringBuilder("SELECT uidp.*,`user`.username_,idp.name_ as idp_name FROM user_idp_bind uidp LEFT JOIN `user` ON uidp.user_id = `user`.id_ LEFT JOIN identity_provider idp ON uidp.idp_id = idp.id_ WHERE 1=1"); + //身份提供商ID + if (StringUtils.isNoneBlank(idpId)) { + builder.append(" AND uidp.idp_id = '").append(idpId).append("'"); + } + //用户ID + if (Objects.nonNull(userId)) { + builder.append(" AND uidp.user_id = '").append(userId).append("'"); + } + //@formatter:on + String sql = builder.toString(); + UserIdpBindPo userIdpBindPo = jdbcTemplate.queryForObject(sql, new UserIdpBindPoMapper()); + return Optional.ofNullable(userIdpBindPo); + } + + /** + * 查询用户身份提供商绑定 + * + * @param userId {@link Long} + * @return {@link Page} + */ + @Override + public Iterable getUserIdpBindList(Long userId) { + //@formatter:off + StringBuilder builder = new StringBuilder("SELECT uidp.*,idp.name_ as idp_name FROM user_idp_bind uidp LEFT JOIN identity_provider idp ON uidp.idp_id = idp.id_ WHERE 1=1"); + //用户ID + if (Objects.nonNull(userId)) { + builder.append(" AND uidp.user_id = '").append(userId).append("'"); + } + //@formatter:on + String sql = builder.toString(); + return jdbcTemplate.query(sql, new UserIdpBindPoMapper()); + } + + /** + * JdbcTemplate + */ + private final JdbcTemplate jdbcTemplate; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/UserRepositoryCustomizedImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/UserRepositoryCustomizedImpl.java new file mode 100644 index 00000000..aceca4fe --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/UserRepositoryCustomizedImpl.java @@ -0,0 +1,355 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account.impl; + +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.List; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.account.UserEntity; +import cn.topiam.employee.common.entity.account.po.UserPO; +import cn.topiam.employee.common.entity.account.query.UserListNotInGroupQuery; +import cn.topiam.employee.common.entity.account.query.UserListQuery; +import cn.topiam.employee.common.repository.account.UserRepositoryCustomized; +import cn.topiam.employee.common.repository.account.impl.mapper.UserEntityMapper; +import cn.topiam.employee.common.repository.account.impl.mapper.UserPoMapper; + +import lombok.AllArgsConstructor; +import static cn.topiam.employee.common.constants.AccountConstants.USER_CACHE_NAME; + +/** + * User Repository Customized + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/29 20:27 + */ +@Repository +@CacheConfig(cacheNames = { USER_CACHE_NAME }) +@AllArgsConstructor +public class UserRepositoryCustomizedImpl implements UserRepositoryCustomized { + + /** + * 获取用户列表 + * + * @param pageable {@link Pageable} + * @param query {@link UserListQuery} + * @return {@link Page} + */ + @SuppressWarnings("DuplicatedCode") + @Override + public Page getUserList(UserListQuery query, Pageable pageable) { + //@formatter:off + StringBuilder builder = new StringBuilder("SELECT `user`.id_, `user`.username_,`user`.password_, `user`.email_, `user`.phone_,`user`.phone_area_code, `user`.full_name ,`user`.nick_name, `user`.avatar_ , `user`.status_, `user`.data_origin, `user`.email_verified, `user`.phone_verified, `user`.shared_secret, `user`.totp_bind , `user`.auth_total, `user`.last_auth_ip, `user`.last_auth_time, `user`.expand_, `user`.external_id , `user`.expire_date,`user`.create_by, `user`.create_time, `user`.update_by , `user`.update_time, `user`.remark_, group_concat(organization_.display_path) AS org_display_path FROM `user` INNER JOIN `organization_member` ON (`user`.id_ = organization_member.user_id) INNER JOIN `organization` organization_ ON (organization_.id_ = organization_member.org_id) WHERE 1=1"); + //组织条件 + if (StringUtils.isNoneBlank(query.getOrganizationId())) { + //包含子节点 + if (Boolean.TRUE.equals(query.getInclSubOrganization())) { + builder.append(" AND FIND_IN_SET('").append(query.getOrganizationId()).append("', REPLACE(organization_.path_, '/', ','))> 0"); + } + else { + builder.append(" AND organization_.id_ = '").append(query.getOrganizationId()).append("'"); + } + } + //用户名条件 + if (StringUtils.isNoneBlank(query.getUsername())) { + builder.append(" AND `user`.username_ LIKE '%").append(query.getUsername()).append("%'"); + } + //姓名 + if (StringUtils.isNoneBlank(query.getFullName())) { + builder.append(" AND `user`.full_name LIKE '%").append(query.getFullName()).append("%'"); + } + //手机号条件 + if (StringUtils.isNoneBlank(query.getPhone())) { + builder.append(" AND `user`.phone_ = '").append(query.getPhone()).append("'"); + } + //邮箱地址条件 + if (StringUtils.isNoneBlank(query.getEmail())) { + builder.append(" AND `user`.email_ LIKE '%").append(query.getEmail()).append("%'"); + } + //状态条件 + if (!Objects.isNull(query.getStatus())) { + builder.append(" AND `user`.status_ = '").append(query.getStatus().getCode()).append("'"); + } + //数据来源 + if (!Objects.isNull(query.getDataOrigin())) { + builder.append(" AND `user`.data_origin = '").append(query.getDataOrigin().getCode()).append("'"); + } + builder.append(" GROUP BY `user`.id_"); + //@formatter:on + String sql = builder.toString(); + List list = jdbcTemplate.query( + builder.append(" LIMIT ").append(pageable.getPageNumber() * pageable.getPageSize()) + .append(",").append(pageable.getPageSize()).toString(), + new UserPoMapper()); + //@formatter:off + String countSql = "SELECT count(*) FROM (" + sql + ") user_"; + //@formatter:on + Integer count = jdbcTemplate.queryForObject(countSql, Integer.class); + return new PageImpl<>(list, pageable, count); + } + + /** + * 获取用户组成员列表 + * + * @param query {@link UserListNotInGroupQuery} + * @param pageable {@link Pageable} + * @return {@link Page} + */ + @Override + public Page getUserListNotInGroupId(UserListNotInGroupQuery query, Pageable pageable) { + //@formatter:off + StringBuilder builder = new StringBuilder("SELECT\n" + + " \t`user`.id_,\n" + + " \t`user`.username_,\n" + + " \t`user`.password_,\n" + + " \t`user`.email_,\n" + + " \t`user`.phone_,\n" + + " \t`user`.phone_area_code,\n" + + " \t`user`.full_name,\n" + + " \t`user`.nick_name,\n" + + " \t`user`.avatar_,\n" + + " \t`user`.status_,\n" + + " \t`user`.data_origin,\n" + + " \t`user`.email_verified,\n" + + " \t`user`.phone_verified,\n" + + " \t`user`.shared_secret,\n" + + " \t`user`.totp_bind,\n" + + " \t`user`.auth_total,\n" + + " \t`user`.last_auth_ip,\n" + + " \t`user`.last_auth_time,\n" + + " \t`user`.expand_,\n" + + " \t`user`.external_id,\n" + + " \t`user`.expire_date,\n" + + " \t`user`.create_by,\n" + + " \t`user`.create_time,\n" + + " \t`user`.update_by,\n" + + " \t`user`.update_time,\n" + + " \t`user`.remark_,\n" + + " \tgroup_concat( organization_.display_path ) AS org_display_path \n" + + " FROM\n" + + " `user` \n" + + " LEFT JOIN `organization_member` ON ( `user`.id_ = organization_member.user_id )\n" + + " LEFT JOIN `organization` organization_ ON ( organization_.id_ = organization_member.org_id ) \n" + + " WHERE\n" + + " \tuser.id_ NOT IN (\n" + + " \tSELECT\n" + + " \t\tu.id_ \n" + + " \tFROM\n" + + " \t\tuser u\n" + + " \t\tINNER JOIN user_group_member ugm ON ugm.user_id = u.id_\n" + + " \t\tINNER JOIN user_group ug ON ug.id_ = ugm.group_id \n" + + " \tWHERE\n" + + " \tug.id_ = '%s' AND ugm.group_id = '%s')".formatted(query.getId(), query.getId())); + if (StringUtils.isNoneBlank(query.getKeyword())) { + builder.append(" AND user.username_ LIKE '%").append(query.getKeyword()).append("%'"); + builder.append(" OR user.full_name LIKE '%").append(query.getKeyword()).append("%'"); + builder.append(" OR user.phone_ = '").append(query.getKeyword()).append("'"); + builder.append(" OR user.email_ = '").append(query.getKeyword()).append("'"); + } + builder.append(" GROUP BY `user`.id_"); + //@formatter:on + String sql = builder.toString(); + List list = jdbcTemplate.query( + builder.append(" LIMIT ").append(pageable.getPageNumber() * pageable.getPageSize()) + .append(",").append(pageable.getPageSize()).toString(), + new UserPoMapper()); + //@formatter:off + String countSql = "SELECT COUNT(*) FROM(" + sql + ") user_"; + //@formatter:on + Integer count = jdbcTemplate.queryForObject(countSql, Integer.class); + return new PageImpl<>(list, pageable, count); + } + + /** + * 根据组织ID查询用户列表 + * + * @param organizationId {@link String} + * @return {@link List} + */ + @Override + public List findAllByOrgId(String organizationId) { + return jdbcTemplate.query( + "SELECT `user`.id_, `user`.username_, user.password_,`user`.email_, `user`.phone_,user.phone_area_code, `user`.full_name ,`user`.nick_name ,`user`.avatar_ , `user`.status_, `user`.data_origin, `user`.email_verified, `user`.shared_secret, `user`.totp_bind , `user`.auth_total, `user`.last_auth_ip, `user`.last_auth_time, `user`.expand_, `user`.external_id , `user`.expire_date,`user`.identity_source_id, `user`.create_by, `user`.create_time, `user`.update_by, `user`.update_time , `user`.remark_ FROM `user` INNER JOIN `organization_member` ON `user`.id_ = organization_member.user_id INNER JOIN `organization` organization_ ON organization_.id_ = organization_member.org_id WHERE 1 = 1 AND organization_.id_ = '" + + organizationId + "'" + "GROUP BY `user`.id_", + new UserEntityMapper()); + } + + /** + * 根据组织ID、数据来源查询用户列表 + * + * @param organizationId {@link String} + * @param identitySourceId {@link Long} + * @return {@link List} + */ + @Override + public List findAllByOrgIdAndIdentitySourceId(String organizationId, + Long identitySourceId) { + return jdbcTemplate.query( + "SELECT `user`.id_, `user`.username_, user.password_,`user`.email_, `user`.phone_,user.phone_area_code, `user`.full_name ,`user`.nick_name ,`user`.avatar_ , `user`.status_, `user`.data_origin, `user`.email_verified, `user`.shared_secret, `user`.totp_bind , `user`.auth_total, `user`.last_auth_ip, `user`.last_auth_time, `user`.expand_, `user`.external_id , `user`.expire_date,`user`.identity_source_id , `user`.create_by, `user`.create_time, `user`.update_by, `user`.update_time , `user`.remark_ FROM `user` INNER JOIN `organization_member` ON `user`.id_ = organization_member.user_id INNER JOIN `organization` organization_ ON organization_.id_ = organization_member.org_id WHERE 1 = 1 AND organization_.id_ = '" + + organizationId + "' AND user.identity_source_id = '" + + identitySourceId + "' GROUP BY `user`.id_", + new UserEntityMapper()); + } + + /** + * 按组织外部 ID 和数据来源查找用户列表 + * + * @param externalId {@link String} + * @param identitySourceId {@link Long} + * @return {@link List} + */ + @Override + public List findAllByOrgExternalIdAndIdentitySourceId(String externalId, + Long identitySourceId) { + return jdbcTemplate.query( + "SELECT `user`.id_, `user`.username_, user.password_,`user`.email_, `user`.phone_,user.phone_area_code, `user`.full_name ,`user`.nick_name ,`user`.avatar_ , `user`.status_, `user`.data_origin, `user`.email_verified,`user`.phone_verified, `user`.shared_secret, `user`.totp_bind , `user`.auth_total, `user`.last_auth_ip, `user`.last_auth_time, `user`.expand_, `user`.external_id , `user`.expire_date, `user`.create_by, `user`.create_time, `user`.update_by, `user`.update_time , `user`.remark_ FROM `user` INNER JOIN `organization_member` ON `user`.id_ = organization_member.user_id INNER JOIN `organization` organization_ ON organization_.id_ = organization_member.org_id WHERE 1 = 1 AND organization_.id_ = '" + + externalId + "' AND user.identity_source_id = '" + + identitySourceId + "' GROUP BY `user`.id_", + new UserPoMapper()); + } + + @Override + public List findAllByOrgIdNotExistAndIdentitySourceId(Long identitySourceId) { + return jdbcTemplate.query( + "SELECT * FROM(SELECT `user`.id_, `user`.username_, user.password_, `user`.email_, `user`.phone_, `user`.phone_area_code, `user`.full_name, `user`.nick_name, `user`.avatar_, `user`.status_, `user`.data_origin, user.identity_source_id,`user`.email_verified, `user`.shared_secret, `user`.totp_bind, `user`.auth_total, `user`.last_auth_ip, `user`.last_auth_time, `user`.expand_, `user`.external_id, `user`.expire_date, `user`.create_by, `user`.create_time, `user`.update_by, `user`.update_time, `user`.remark_ FROM `user` LEFT JOIN `organization_member` ON `user`.id_ = organization_member.user_id LEFT JOIN `organization` organization_ ON organization_.id_ = organization_member.org_id WHERE `organization_member`.user_id IS NULL) user WHERE user.identity_source_id = '" + + identitySourceId + "'" + "GROUP BY `user`.id_", + new UserEntityMapper()); + } + + /** + * 批量新增或更新 + * + * @param list {@link List} + */ + @Override + @CacheEvict(allEntries = true) + public void batchSave(List list) { + //@formatter:off + jdbcTemplate.batchUpdate( + "INSERT INTO user (id_, username_, password_, email_, phone_, phone_area_code, full_name,nick_name, avatar_, external_id, expire_date, status_, email_verified, phone_verified,shared_secret, auth_total,last_auth_ip,last_auth_time,expand_,data_origin,identity_source_id,totp_bind,last_update_password_time ,create_by,create_time,update_by,update_time,remark_) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ", + new BatchPreparedStatementSetter() { + + @Override + public void setValues(@NotNull PreparedStatement ps, int i) throws SQLException { + UserEntity entity = list.get(i); + ps.setLong(1, entity.getId()); + ps.setString(2, entity.getUsername()); + ps.setString(3, entity.getPassword()); + ps.setObject(4, StringUtils.isBlank(entity.getEmail())?null:entity.getEmail()); + ps.setObject(5, StringUtils.isBlank(entity.getPhone())?null:entity.getPhone()); + ps.setString(6, entity.getPhoneAreaCode()); + ps.setString(7, entity.getFullName()); + ps.setString(8, entity.getNickName()); + ps.setString(9, entity.getAvatar()); + ps.setString(10, entity.getExternalId()); + ps.setDate(11, Date.valueOf(entity.getExpireDate())); + ps.setString(12, entity.getStatus().getCode()); + ps.setBoolean(13, !Objects.isNull(entity.getEmailVerified()) && entity.getEmailVerified()); + ps.setBoolean(14, !Objects.isNull(entity.getPhoneVerified()) && entity.getPhoneVerified()); + ps.setString(15, entity.getSharedSecret()); + ps.setLong(16, Objects.isNull(entity.getAuthTotal())?0L:entity.getAuthTotal()); + ps.setString(17, entity.getLastAuthIp()); + ps.setTimestamp(18, !Objects.isNull(entity.getLastAuthTime()) ? Timestamp.valueOf(entity.getLastAuthTime()) : null); + ps.setString(19, entity.getExpand()); + ps.setString(20, entity.getDataOrigin().getCode()); + ps.setObject(21, entity.getIdentitySourceId()); + ps.setBoolean(22, !Objects.isNull(entity.getTotpBind()) && entity.getTotpBind()); + ps.setTimestamp(23, !Objects.isNull(entity.getLastUpdatePasswordTime())?Timestamp.valueOf(entity.getLastUpdatePasswordTime()):null); + ps.setString(24, entity.getCreateBy()); + ps.setTimestamp(25, Timestamp.valueOf(entity.getCreateTime())); + ps.setString(26, entity.getUpdateBy()); + ps.setTimestamp(27, Timestamp.valueOf(entity.getUpdateTime())); + ps.setString(28, entity.getRemark()); + } + + @Override + public int getBatchSize() { + return list.size(); + } + }); + //@formatter:on + } + + @Override + @CacheEvict(allEntries = true) + public void batchUpdate(List list) { + //@formatter:off + jdbcTemplate.batchUpdate( + "UPDATE user SET username_=?, password_=?, email_=?, phone_=?, phone_area_code=?,full_name=?,nick_name=?, avatar_=?, external_id=?, expire_date=?, status_=?, email_verified=?, phone_verified=?,shared_secret=?, auth_total=?,last_auth_ip=?,last_auth_time=?,expand_=?,data_origin=?,identity_source_id=?,totp_bind=?,last_update_password_time =?,create_by=?,create_time=?,update_by=?,update_time=?,remark_=? WHERE id_=?", + new BatchPreparedStatementSetter() { + + @Override + public void setValues(@NotNull PreparedStatement ps, int i) throws SQLException { + UserEntity entity = list.get(i); + ps.setString(1, entity.getUsername()); + ps.setString(2, entity.getPassword()); + ps.setObject(3, StringUtils.isBlank(entity.getEmail())?null:entity.getEmail()); + ps.setObject(4, StringUtils.isBlank(entity.getPhone())?null:entity.getPhone()); + ps.setString(5, entity.getPhoneAreaCode()); + ps.setString(6, entity.getFullName()); + ps.setString(7, entity.getNickName()); + ps.setString(8, entity.getAvatar()); + ps.setString(9, entity.getExternalId()); + ps.setDate(10, Date.valueOf(entity.getExpireDate())); + ps.setString(11, entity.getStatus().getCode()); + ps.setBoolean(12, !Objects.isNull(entity.getEmailVerified()) && entity.getEmailVerified()); + ps.setBoolean(13, !Objects.isNull(entity.getPhoneVerified()) && entity.getPhoneVerified()); + ps.setString(14, entity.getSharedSecret()); + ps.setLong(15, entity.getAuthTotal()); + ps.setString(16, entity.getLastAuthIp()); + ps.setTimestamp(17, !Objects.isNull(entity.getLastAuthTime()) ? Timestamp.valueOf(entity.getLastAuthTime()) : null); + ps.setString(18, entity.getExpand()); + ps.setString(19, entity.getDataOrigin().getCode()); + ps.setObject(20, entity.getIdentitySourceId()); + ps.setBoolean(21, !Objects.isNull(entity.getTotpBind()) && entity.getTotpBind()); + ps.setTimestamp(22, !Objects.isNull(entity.getLastUpdatePasswordTime())?Timestamp.valueOf(entity.getLastUpdatePasswordTime()):null); + ps.setString(23, entity.getCreateBy()); + ps.setTimestamp(24, Timestamp.valueOf(entity.getCreateTime())); + ps.setString(25, entity.getUpdateBy()); + ps.setTimestamp(26, Timestamp.valueOf(entity.getUpdateTime())); + ps.setString(27, entity.getRemark()); + ps.setLong(28, entity.getId()); + } + + @Override + public int getBatchSize() { + return list.size(); + } + }); + //@formatter:on + } + + /** + * JdbcTemplate + */ + private final JdbcTemplate jdbcTemplate; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/mapper/UserEntityMapper.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/mapper/UserEntityMapper.java new file mode 100644 index 00000000..f1a3dac6 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/mapper/UserEntityMapper.java @@ -0,0 +1,83 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account.impl.mapper; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.lang.NonNull; + +import cn.topiam.employee.common.entity.account.UserEntity; +import cn.topiam.employee.common.enums.DataOrigin; +import cn.topiam.employee.common.enums.UserStatus; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/2/13 22:25 + */ +@SuppressWarnings("DuplicatedCode") +public class UserEntityMapper implements RowMapper { + /** + * Implementations must implement this method to map each row of data + * in the ResultSet. This method should not call {@code next()} on + * the ResultSet; it is only supposed to map values of the current row. + * + * @param rs the ResultSet to map (pre-initialized for the current row) + * @param rowNum the number of the current row + * @return the result object for the current row (may be {@code null}) + * @throws SQLException if an SQLException is encountered getting + * column values (that is, there's no need to catch SQLException) + */ + @Override + public UserEntity mapRow(@NonNull ResultSet rs, int rowNum) throws SQLException { + //@formatter:off + UserEntity user = new UserEntity(); + user.setId(rs.getLong("id_")); + user.setUsername(rs.getString("username_")); + user.setPassword(rs.getString("password_")); + user.setEmail(rs.getString("email_")); + user.setPhone(rs.getString("phone_")); + user.setPhoneAreaCode(rs.getString("phone_area_code")); + user.setFullName(rs.getString("full_name")); + user.setNickName(rs.getString("nick_name")); + user.setAvatar(rs.getString("avatar_")); + user.setStatus(UserStatus.getStatus(rs.getString("status_"))); + user.setDataOrigin(DataOrigin.getType(rs.getString("data_origin"))); + user.setEmailVerified(rs.getBoolean("email_verified")); + user.setSharedSecret(rs.getString("shared_secret")); + user.setTotpBind(rs.getBoolean("totp_bind")); + user.setAuthTotal(rs.getLong("auth_total")); + user.setLastAuthIp(rs.getString("last_auth_ip")); + user.setLastAuthTime(ObjectUtils.isNotEmpty(rs.getTimestamp("last_auth_time")) ? rs.getTimestamp("last_auth_time").toLocalDateTime() : null); + user.setExpand(rs.getString("expand_")); + user.setExternalId(rs.getString("external_id")); + user.setIdentitySourceId(rs.getLong("identity_source_id")); + user.setExpireDate(ObjectUtils.isNotEmpty(rs.getTimestamp("expire_date")) ? rs.getDate("expire_date").toLocalDate() : null); + user.setLastAuthTime(ObjectUtils.isNotEmpty(rs.getTimestamp("last_auth_time")) ? rs.getTimestamp("last_auth_time").toLocalDateTime() : null); + //额外数据 + user.setCreateBy(rs.getString("create_by")); + user.setCreateTime(ObjectUtils.isNotEmpty(rs.getTimestamp("create_time")) ? rs.getTimestamp("create_time").toLocalDateTime() : null); + user.setUpdateBy(rs.getString("update_by")); + user.setUpdateTime(ObjectUtils.isNotEmpty(rs.getTimestamp("update_time")) ? rs.getTimestamp("update_time").toLocalDateTime() : null); + user.setRemark(rs.getString("remark_")); + return user; + //@formatter:on + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/mapper/UserIdpBindPoMapper.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/mapper/UserIdpBindPoMapper.java new file mode 100644 index 00000000..0b0f5851 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/mapper/UserIdpBindPoMapper.java @@ -0,0 +1,89 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account.impl.mapper; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.lang.NonNull; + +import cn.topiam.employee.common.entity.account.po.UserIdpBindPo; +import cn.topiam.employee.common.enums.IdentityProviderType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/2/13 22:25 + */ +@SuppressWarnings("DuplicatedCode") +public class UserIdpBindPoMapper implements RowMapper { + /** + * Implementations must implement this method to map each row of data + * in the ResultSet. This method should not call {@code next()} on + * the ResultSet; it is only supposed to map values of the current row. + * + * @param rs the ResultSet to map (pre-initialized for the current row) + * @param rowNum the number of the current row + * @return the result object for the current row (may be {@code null}) + * @throws SQLException if an SQLException is encountered getting + * column values (that is, there's no need to catch SQLException) + */ + @Override + public UserIdpBindPo mapRow(@NonNull ResultSet rs, int rowNum) throws SQLException { + //@formatter:off + UserIdpBindPo userIdpBindPo = new UserIdpBindPo(); + userIdpBindPo.setId(rs.getLong("id_")); + userIdpBindPo.setUserId(rs.getLong("user_id")); + userIdpBindPo.setOpenId(rs.getString("open_id")); + userIdpBindPo.setIdpId(rs.getString("idp_id")); + userIdpBindPo.setIdpType(IdentityProviderType.getType(rs.getString("idp_type"))); + userIdpBindPo.setBindTime(rs.getTimestamp("bind_time").toLocalDateTime()); + userIdpBindPo.setAdditionInfo(rs.getString("addition_info")); + if (isExistColumn(rs, "username_")) { + userIdpBindPo.setUserName(rs.getString("username_")); + } + userIdpBindPo.setIdpName(rs.getString("idp_name")); + //额外数据 + userIdpBindPo.setCreateBy(rs.getString("create_by")); + userIdpBindPo.setCreateTime(ObjectUtils.isNotEmpty(rs.getTimestamp("create_time")) ? rs.getTimestamp("create_time").toLocalDateTime() : null); + userIdpBindPo.setUpdateBy(rs.getString("update_by")); + userIdpBindPo.setUpdateTime(ObjectUtils.isNotEmpty(rs.getTimestamp("update_time")) ? rs.getTimestamp("update_time").toLocalDateTime() : null); + userIdpBindPo.setRemark(rs.getString("remark_")); + //@formatter:on + return userIdpBindPo; + } + + /** + * 判断查询结果集中是否存在某列 + * + * @param rs 查询结果集 + * @param columnName 列名 + * @return true 存在; false 不存在 + */ + private boolean isExistColumn(ResultSet rs, String columnName) { + try { + if (rs.findColumn(columnName) > 0) { + return true; + } + } catch (SQLException e) { + return false; + } + return false; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/mapper/UserPoMapper.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/mapper/UserPoMapper.java new file mode 100644 index 00000000..a4a120b7 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/account/impl/mapper/UserPoMapper.java @@ -0,0 +1,84 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.account.impl.mapper; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.lang.NonNull; + +import cn.topiam.employee.common.entity.account.po.UserPO; +import cn.topiam.employee.common.enums.DataOrigin; +import cn.topiam.employee.common.enums.UserStatus; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/2/13 22:25 + */ +@SuppressWarnings("DuplicatedCode") +public class UserPoMapper implements RowMapper { + /** + * Implementations must implement this method to map each row of data + * in the ResultSet. This method should not call {@code next()} on + * the ResultSet; it is only supposed to map values of the current row. + * + * @param rs the ResultSet to map (pre-initialized for the current row) + * @param rowNum the number of the current row + * @return the result object for the current row (may be {@code null}) + * @throws SQLException if an SQLException is encountered getting + * column values (that is, there's no need to catch SQLException) + */ + @Override + public UserPO mapRow(@NonNull ResultSet rs, int rowNum) throws SQLException { + //@formatter:off + UserPO user = new UserPO(); + user.setId(rs.getLong("id_")); + user.setUsername(rs.getString("username_")); + user.setPassword(rs.getString("password_")); + user.setEmail(rs.getString("email_")); + user.setPhone(rs.getString("phone_")); + user.setPhoneAreaCode(rs.getString("phone_area_code")); + user.setFullName(rs.getString("full_name")); + user.setNickName(rs.getString("nick_name")); + user.setAvatar(rs.getString("avatar_")); + user.setStatus(UserStatus.getStatus(rs.getString("status_"))); + user.setDataOrigin(DataOrigin.getType(rs.getString("data_origin"))); + user.setEmailVerified(rs.getBoolean("email_verified")); + user.setPhoneVerified(rs.getBoolean("phone_verified")); + user.setSharedSecret(rs.getString("shared_secret")); + user.setTotpBind(rs.getBoolean("totp_bind")); + user.setAuthTotal(rs.getLong("auth_total")); + user.setLastAuthIp(rs.getString("last_auth_ip")); + user.setLastAuthTime(ObjectUtils.isNotEmpty(rs.getTimestamp("last_auth_time")) ? rs.getTimestamp("last_auth_time").toLocalDateTime() : null); + user.setExpand(rs.getString("expand_")); + user.setExternalId(rs.getString("external_id")); + user.setExpireDate(ObjectUtils.isNotEmpty(rs.getTimestamp("expire_date")) ? rs.getDate("expire_date").toLocalDate() : null); + user.setLastAuthTime(ObjectUtils.isNotEmpty(rs.getTimestamp("last_auth_time")) ? rs.getTimestamp("last_auth_time").toLocalDateTime() : null); + user.setOrgDisplayPath(rs.getString("org_display_path")); + //额外数据 + user.setCreateBy(rs.getString("create_by")); + user.setCreateTime(ObjectUtils.isNotEmpty(rs.getTimestamp("create_time")) ? rs.getTimestamp("create_time").toLocalDateTime() : null); + user.setUpdateBy(rs.getString("update_by")); + user.setUpdateTime(ObjectUtils.isNotEmpty(rs.getTimestamp("update_time")) ? rs.getTimestamp("update_time").toLocalDateTime() : null); + user.setRemark(rs.getString("remark_")); + //@formatter:on + return user; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppAccessPolicyRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppAccessPolicyRepository.java new file mode 100644 index 00000000..f184fd19 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppAccessPolicyRepository.java @@ -0,0 +1,57 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.app.AppAccessPolicyEntity; +import cn.topiam.employee.common.enums.PolicySubjectType; + +/** + * 应用授权策略 Repository + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/4 19:54 + */ +@Repository +public interface AppAccessPolicyRepository extends JpaRepository, + QuerydslPredicateExecutor, + AppAccessPolicyRepositoryCustomized { + /** + * 根据应用ID删除所有数据 + * + * @param appId {@link Long} + */ + void deleteAllByAppId(Long appId); + + /** + * 根据应用ID、主体ID,主体类型查询 + * + * @param appId {@link Long} + * @param subjectId {@link String} + * @param subjectType {@link PolicySubjectType} + * @return {@link AppAccessPolicyEntity} + */ + Optional findByAppIdAndSubjectIdAndSubjectType(Long appId, + String subjectId, + PolicySubjectType subjectType); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppAccessPolicyRepositoryCustomized.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppAccessPolicyRepositoryCustomized.java new file mode 100644 index 00000000..99b8f19c --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppAccessPolicyRepositoryCustomized.java @@ -0,0 +1,43 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import cn.topiam.employee.common.entity.account.query.UserListQuery; +import cn.topiam.employee.common.entity.app.po.AppAccessPolicyPO; +import cn.topiam.employee.common.entity.app.query.AppAccessPolicyQuery; + +/** + * 应用访问策略 Repository Customized + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/26 23:40 + */ +public interface AppAccessPolicyRepositoryCustomized { + + /** + * 获取应用授权策略列表 + * + * @param pageable {@link Pageable} + * @param query {@link UserListQuery} + * @return {@link Page} + */ + Page getAppPolicyList(AppAccessPolicyQuery query, Pageable pageable); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppAccountRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppAccountRepository.java new file mode 100644 index 00000000..d712f088 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppAccountRepository.java @@ -0,0 +1,90 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app; + +import java.util.Optional; + +import org.jetbrains.annotations.NotNull; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.app.AppAccountEntity; +import static cn.topiam.employee.common.constants.ProtocolConstants.APP_ACCOUNT_CACHE_NAME; + +/** + * 应用账户 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/3 22:26 + */ +@Repository +@CacheConfig(cacheNames = { APP_ACCOUNT_CACHE_NAME }) +public interface AppAccountRepository extends JpaRepository, + QuerydslPredicateExecutor, + AppAccountRepositoryCustomized { + /** + * save + * + * @param entity must not be {@literal null}. + * @param {@link S} + * @return {@link AppAccountEntity} + */ + @NotNull + @Override + @CacheEvict(allEntries = true) + S save(@NotNull S entity); + + /** + * delete + * + * @param id must not be {@literal null}. + */ + @CacheEvict(allEntries = true) + @Override + void deleteById(@NotNull Long id); + + /** + * 根据应用ID,用户ID查询应用账户 + * + * @param appId {@link Long} + * @param userId {@link Long} + * @return {@link Optional} + */ + @Cacheable + Optional findByAppIdAndUserId(Long appId, Long userId); + + /** + * 根据appid删除所有的数据 + * + * @param appId {@link Long} + */ + @CacheEvict(allEntries = true) + void deleteAllByAppId(Long appId); + + /** + * 根据userId 删除用户数据 + * + * @param userId {@link Long} + */ + @CacheEvict(allEntries = true) + void deleteByUserId(Long userId); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppAccountRepositoryCustomized.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppAccountRepositoryCustomized.java new file mode 100644 index 00000000..d3a26a62 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppAccountRepositoryCustomized.java @@ -0,0 +1,43 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import cn.topiam.employee.common.entity.account.query.UserListQuery; +import cn.topiam.employee.common.entity.app.po.AppAccountPO; +import cn.topiam.employee.common.entity.app.query.AppAccountQuery; + +/** + * 应用账户 Repository Customized + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/26 23:40 + */ +public interface AppAccountRepositoryCustomized { + + /** + * 获取应用账户列表 + * + * @param pageable {@link Pageable} + * @param query {@link UserListQuery} + * @return {@link Page} + */ + Page getAppAccountList(AppAccountQuery query, Pageable pageable); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppCertRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppCertRepository.java new file mode 100644 index 00000000..2d489bef --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppCertRepository.java @@ -0,0 +1,92 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app; + +import java.util.Optional; + +import org.jetbrains.annotations.NotNull; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; + +import cn.topiam.employee.common.entity.app.AppCertEntity; +import cn.topiam.employee.common.entity.app.AppOidcConfigEntity; +import cn.topiam.employee.common.enums.app.AppCertUsingType; +import static cn.topiam.employee.common.constants.ProtocolConstants.APP_CERT_CACHE_NAME; + +/** + * AppCertificateRepository + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/31 20:52 + */ +@CacheConfig(cacheNames = { APP_CERT_CACHE_NAME }) +public interface AppCertRepository extends JpaRepository, + QuerydslPredicateExecutor { + /** + * 根据应用ID查询证书 + * + * @param appId {@link Long} + * @param usingType {@link AppCertUsingType} + * @return {@link AppCertEntity} + */ + @Cacheable(key = "#p1.code+':'+#p0", unless = "#result==null") + Optional findByAppIdAndUsingType(Long appId, AppCertUsingType usingType); + + /** + * save + * + * @param entity must not be {@literal null}. + * @param {@link S} + * @return {@link AppCertEntity} + */ + @NotNull + @Override + @CacheEvict(allEntries = true) + S save(@NotNull S entity); + + /** + * delete + * + * @param id must not be {@literal null}. + */ + @CacheEvict(allEntries = true) + @Override + void deleteById(@NotNull Long id); + + /** + * 根据应用ID删除应用 + * + * @param appId {@link Long} + */ + @CacheEvict(allEntries = true) + void deleteByAppId(Long appId); + + /** + * find by id + * + * @param id must not be {@literal null}. + * @return {@link AppOidcConfigEntity} + */ + @NotNull + @Override + @Cacheable + Optional findById(@NotNull Long id); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppOidcConfigRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppOidcConfigRepository.java new file mode 100644 index 00000000..7d1a278d --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppOidcConfigRepository.java @@ -0,0 +1,76 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app; + +import java.util.Optional; + +import org.jetbrains.annotations.NotNull; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.app.AppOidcConfigEntity; +import static cn.topiam.employee.common.constants.ProtocolConstants.OIDC_CONFIG_CACHE_NAME; + +/** + * @author TopIAM + */ +@Repository +@CacheConfig(cacheNames = { OIDC_CONFIG_CACHE_NAME }) +public interface AppOidcConfigRepository extends JpaRepository, + QuerydslPredicateExecutor, + AppOidcConfigRepositoryCustomized { + /** + * 按应用 ID 删除 + * + * @param appId {@link Long} + */ + @CacheEvict(allEntries = true) + void deleteByAppId(Long appId); + + /** + * delete + * + * @param id must not be {@literal null}. + */ + @CacheEvict(allEntries = true) + @Override + void deleteById(@NotNull Long id); + + /** + * save + * + * @param entity must not be {@literal null}. + * @param {@link S} + * @return {@link AppOidcConfigEntity} + */ + @NotNull + @Override + @CacheEvict(allEntries = true) + S save(@NotNull S entity); + + /** + * 根据应用ID获取配置 + * + * @param appId {@link Long} + * @return {@link AppOidcConfigEntity} + */ + Optional findByAppId(Long appId); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppOidcConfigRepositoryCustomized.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppOidcConfigRepositoryCustomized.java new file mode 100644 index 00000000..70718744 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppOidcConfigRepositoryCustomized.java @@ -0,0 +1,50 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app; + +import cn.topiam.employee.common.entity.app.po.AppOidcConfigPO; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/10 22:32 + */ +public interface AppOidcConfigRepositoryCustomized { + /** + * 根据应用ID获取 + * + * @param appId {@link Long} + * @return {@link AppOidcConfigPO} + */ + AppOidcConfigPO getByAppId(Long appId); + + /** + * 根据应用 Client 获取 + * + * @param clientId {@link String} + * @return {@link AppOidcConfigPO} + */ + AppOidcConfigPO getByClientId(String clientId); + + /** + * 根据应用编码查询应用配置 + * + * @param appCode {@link String} + * @return {@link AppOidcConfigPO} + */ + AppOidcConfigPO findByAppCode(String appCode); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionActionRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionActionRepository.java new file mode 100644 index 00000000..740d2ed2 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionActionRepository.java @@ -0,0 +1,46 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app; + +import java.util.List; + +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.app.AppPermissionActionEntity; +import cn.topiam.employee.common.entity.app.AppPermissionResourceEntity; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2021/11/22 23:06 + */ +@Repository +public interface AppPermissionActionRepository extends + CrudRepository, + PagingAndSortingRepository, + QuerydslPredicateExecutor { + /** + * findAllByResource + * + * @param resource {@link AppPermissionResourceEntity} + * @return {@link List} + */ + List findAllByResource(AppPermissionResourceEntity resource); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionPolicyRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionPolicyRepository.java new file mode 100644 index 00000000..fa684653 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionPolicyRepository.java @@ -0,0 +1,75 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app; + +import java.io.Serializable; +import java.util.Collection; + +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.common.entity.app.AppPermissionPolicyEntity; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2021/11/4 22:44 + */ +@Repository +public interface AppPermissionPolicyRepository extends AppPermissionPolicyRepositoryCustomized, + CrudRepository, + PagingAndSortingRepository, + QuerydslPredicateExecutor { + /** + * 按主体 ID 删除所有 + * + * @param subjectIds {@link String} + */ + void deleteAllBySubjectIdIn(Collection subjectIds); + + /** + * 按客体 ID 删除所有 + * + * @param objectIds {@link String} + */ + void deleteAllByObjectIdIn(Collection objectIds); + + /** + * 根据主体删除所有 + * + * @param objectId + */ + void deleteAllByObjectId(Long objectId); + + /** + * 更新启用/禁用 + * + * @param id {@link Serializable} + * @param status {@link Boolean} + * @return {@link Integer} + */ + @Transactional(rollbackFor = Exception.class) + @Modifying + @Query(value = "UPDATE AppPermissionResourceEntity set enabled =:status WHERE id =:id") + Integer updateStatus(@Param(value = "id") Long id, @Param(value = "status") Boolean status); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionPolicyRepositoryCustomized.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionPolicyRepositoryCustomized.java new file mode 100644 index 00000000..974661a7 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionPolicyRepositoryCustomized.java @@ -0,0 +1,39 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import cn.topiam.employee.common.entity.app.po.AppPermissionPolicyPO; +import cn.topiam.employee.common.entity.app.query.AppPolicyQuery; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2021/11/4 22:44 + */ +public interface AppPermissionPolicyRepositoryCustomized { + /** + * 分页查询权限策略 + * + * @param query {@link AppPolicyQuery} + * @param request {@link Pageable} + * @return {@link AppPermissionPolicyPO} + */ + Page findPage(AppPolicyQuery query, Pageable request); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionResourceRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionResourceRepository.java new file mode 100644 index 00000000..0817df81 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionResourceRepository.java @@ -0,0 +1,43 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app; + +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.app.AppPermissionResourceEntity; +import cn.topiam.employee.common.repository.authorization.ResourceRepositoryCustomized; + +/** + *

+ * 资源权限 Repository 接口 SystemRepositoryCustomized + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-10 + */ +@Repository +public interface AppPermissionResourceRepository extends + CrudRepository, + ResourceRepositoryCustomized, + PagingAndSortingRepository, + QuerydslPredicateExecutor { + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionRoleRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionRoleRepository.java new file mode 100644 index 00000000..e5ab1a5e --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppPermissionRoleRepository.java @@ -0,0 +1,55 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app; + +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.common.entity.app.AppPermissionRoleEntity; +import cn.topiam.employee.common.repository.authorization.RoleRepositoryCustomized; + +/** + *

+ * 角色表 Repository 接口 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-10 + */ +@Repository +public interface AppPermissionRoleRepository extends CrudRepository, + RoleRepositoryCustomized, + PagingAndSortingRepository, + QuerydslPredicateExecutor { + /** + * 更新角色状态 + * + * @param id {@link String} + * @param enabled {@link String} + */ + @Transactional(rollbackFor = Exception.class) + @Modifying + @Query(value = "update app_permission_role set is_enabled = ?2 where id_ = ?1", nativeQuery = true) + void updateStatus(@Param(value = "id") String id, @Param(value = "enabled") Boolean enabled); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppRepository.java new file mode 100644 index 00000000..733bec8a --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppRepository.java @@ -0,0 +1,99 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app; + +import java.util.Optional; + +import org.jetbrains.annotations.NotNull; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.app.AppEntity; +import static cn.topiam.employee.common.constants.AppConstants.APP_CACHE_NAME; + +/** + * @author TopIAM + */ +@Repository +@CacheConfig(cacheNames = { APP_CACHE_NAME }) +public interface AppRepository extends JpaRepository, + QuerydslPredicateExecutor, AppRepositoryCustomized { + /** + * 根据应用ID查询已启用应用 + * + * @param id {@link Long} + * @return {@link AppEntity} + */ + @Cacheable + Optional findByIdAndEnabledTrue(Long id); + + /** + * save + * + * @param entity must not be {@literal null}. + * @param {@link S} + * @return {@link AppEntity} + */ + @NotNull + @Override + @CacheEvict(allEntries = true) + S save(@NotNull S entity); + + /** + * delete + * + * @param id must not be {@literal null}. + */ + @Override + @CacheEvict(allEntries = true) + void deleteById(@NotNull Long id); + + /** + * find by id + * + * @param id must not be {@literal null}. + * @return {@link AppEntity} + */ + @NotNull + @Override + @Cacheable + Optional findById(@NotNull Long id); + + /** + * 根据clientId获取配置 + * + * @param clientId {@link String} + * @return {@link AppEntity} + */ + @Cacheable + AppEntity findByClientId(String clientId); + + /** + * findByCode + * + * @param appCode {@link String} + * @return {@link AppEntity} + */ + @NotNull + @Cacheable + Optional findByCode(String appCode); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppRepositoryCustomized.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppRepositoryCustomized.java new file mode 100644 index 00000000..fa47ff2f --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppRepositoryCustomized.java @@ -0,0 +1,56 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app; + +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.query.Param; + +import cn.topiam.employee.common.entity.app.AppEntity; + +/** + * 应用 Repository Customized + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/26 23:40 + */ +public interface AppRepositoryCustomized { + + /** + * 更新应用状态 + * + * @param id {@link Long} + * @param enabled {@link Boolean} + * @return {@link Boolean} + */ + Integer updateAppStatus(@Param(value = "id") Long id, + @Param(value = "enabled") Boolean enabled); + + /** + * + * 获取我的应用列表 + * + * @param name {@link String} + * @param userId {@link Long} + * @param pageable {@link Pageable} + * @return {@link List} + */ + Page getAppList(Long userId, String name, Pageable pageable); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppSaml2ConfigRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppSaml2ConfigRepository.java new file mode 100644 index 00000000..03aaec12 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppSaml2ConfigRepository.java @@ -0,0 +1,76 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app; + +import java.util.Optional; + +import org.jetbrains.annotations.NotNull; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.app.AppSaml2ConfigEntity; +import static cn.topiam.employee.common.constants.ProtocolConstants.SAML2_CONFIG_CACHE_NAME; + +/** + * @author TopIAM + */ +@Repository +@CacheConfig(cacheNames = { SAML2_CONFIG_CACHE_NAME }) +public interface AppSaml2ConfigRepository extends JpaRepository, + QuerydslPredicateExecutor, + AppSaml2ConfigRepositoryCustomized { + /** + * 按应用 ID 删除 + * + * @param appId {@link Long} + */ + @CacheEvict(allEntries = true) + void deleteByAppId(Long appId); + + /** + * delete + * + * @param id must not be {@literal null}. + */ + @CacheEvict(allEntries = true) + @Override + void deleteById(@NotNull Long id); + + /** + * save + * + * @param entity must not be {@literal null}. + * @param {@link S} + * @return {@link AppSaml2ConfigEntity} + */ + @NotNull + @Override + @CacheEvict(allEntries = true) + S save(@NotNull S entity); + + /** + * 根据应用ID获取配置 + * + * @param id {@link Long} + * @return {@link AppSaml2ConfigEntity} + */ + Optional findByAppId(Long id); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppSaml2ConfigRepositoryCustomized.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppSaml2ConfigRepositoryCustomized.java new file mode 100644 index 00000000..49476277 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppSaml2ConfigRepositoryCustomized.java @@ -0,0 +1,42 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app; + +import cn.topiam.employee.common.entity.app.po.AppSaml2ConfigPO; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/10 22:32 + */ +public interface AppSaml2ConfigRepositoryCustomized { + /** + * 根据应用ID获取 + * + * @param appId {@link Long} + * @return {@link AppSaml2ConfigPO} + */ + AppSaml2ConfigPO getByAppId(Long appId); + + /** + * 根据应用code获取应用 + * + * @param appCode {@link String} + * @return {@link AppSaml2ConfigPO} + */ + AppSaml2ConfigPO findByAppCode(String appCode); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppAccessPolicyRepositoryCustomizedImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppAccessPolicyRepositoryCustomizedImpl.java new file mode 100644 index 00000000..100827c4 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppAccessPolicyRepositoryCustomizedImpl.java @@ -0,0 +1,97 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app.impl; + +import java.util.List; + +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.account.query.UserListQuery; +import cn.topiam.employee.common.entity.app.po.AppAccessPolicyPO; +import cn.topiam.employee.common.entity.app.query.AppAccessPolicyQuery; +import cn.topiam.employee.common.repository.app.AppAccessPolicyRepositoryCustomized; +import cn.topiam.employee.common.repository.app.impl.mapper.AppAccessPolicyPoMapper; + +import lombok.AllArgsConstructor; + +/** + * AppPolicy Repository Customized + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/29 20:27 + */ +@Repository +@AllArgsConstructor +public class AppAccessPolicyRepositoryCustomizedImpl implements + AppAccessPolicyRepositoryCustomized { + + /** + * 获取应用授权策略列表 + * + * @param pageable {@link Pageable} + * @param query {@link UserListQuery} + * @return {@link Page} + */ + @Override + public Page getAppPolicyList(AppAccessPolicyQuery query, Pageable pageable) { + //@formatter:off + StringBuilder builder = new StringBuilder("SELECT a.id_,a.app_id,a.subject_id,a.subject_type,a.create_time,`subject`.name_,app.name_ as app_name,app.type_ as app_type,app.template_ as app_template,app.protocol_ as app_protocol FROM app_access_policy a LEFT JOIN app ON a.app_id = app.id_ LEFT JOIN "); + builder.append("(SELECT id_,name_ FROM user_group UNION ALL SELECT id_,name_ FROM organization UNION ALL SELECT id_,username_ as name_ FROM `user`) `subject` ON a.subject_id = `subject`.id_ WHERE 1=1"); + if (ObjectUtils.isNotEmpty(query.getSubjectType())) { + builder.append(" AND a.subject_type = '").append(query.getSubjectType().getCode()).append("'"); + } + //主体名称 + if (StringUtils.isNotEmpty(query.getSubjectName())) { + builder.append(" AND subject.name_ like '%").append(query.getSubjectName()).append("%'"); + } + //主体ID + if (StringUtils.isNotBlank(query.getSubjectId())) { + builder.append(" AND a.subject_id = '").append(query.getSubjectId()).append("'"); + } + //应用ID + if (StringUtils.isNotEmpty(query.getAppId())) { + builder.append(" AND a.app_id = '").append(query.getAppId()).append("'"); + } + //应用名称 + if (StringUtils.isNotBlank(query.getAppName())) { + builder.append(" AND app.name_ like '%").append(query.getAppName()).append("'"); + } + //@formatter:on + String sql = builder.toString(); + List list = jdbcTemplate.query( + builder.append(" LIMIT ").append(pageable.getPageNumber() * pageable.getPageSize()) + .append(",").append(pageable.getPageSize()).toString(), + new AppAccessPolicyPoMapper()); + //@formatter:off + String countSql = "SELECT count(*) FROM (" + sql + ") app_access_policy_"; + //@formatter:on + Integer count = jdbcTemplate.queryForObject(countSql, Integer.class); + return new PageImpl<>(list, pageable, count); + } + + /** + * JdbcTemplate + */ + private final JdbcTemplate jdbcTemplate; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppAccountRepositoryCustomizedImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppAccountRepositoryCustomizedImpl.java new file mode 100644 index 00000000..20987bb3 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppAccountRepositoryCustomizedImpl.java @@ -0,0 +1,98 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app.impl; + +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.account.query.UserListQuery; +import cn.topiam.employee.common.entity.app.po.AppAccountPO; +import cn.topiam.employee.common.entity.app.query.AppAccountQuery; +import cn.topiam.employee.common.repository.app.AppAccountRepositoryCustomized; +import cn.topiam.employee.common.repository.app.impl.mapper.AppAccountPoMapper; + +import lombok.AllArgsConstructor; + +/** + * AppAccount Repository Customized + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/29 20:27 + */ +@Repository +@AllArgsConstructor +public class AppAccountRepositoryCustomizedImpl implements AppAccountRepositoryCustomized { + + /** + * 获取应用账户列表 + * + * @param pageable {@link Pageable} + * @param query {@link UserListQuery} + * @return {@link Page} + */ + @Override + public Page getAppAccountList(AppAccountQuery query, Pageable pageable) { + //@formatter:off + StringBuilder builder = new StringBuilder("SELECT a.id_,a.app_id,a.user_id,a.account_,a.create_time,u.username_,p.name_ as app_name,p.type_ as app_type,p.template_ as app_template,p.protocol_ as app_protocol FROM app_account a LEFT JOIN `user` u ON a.user_id = u.id_ LEFT JOIN app p ON a.app_id = p.id_ WHERE 1=1"); + //用户名 + if (StringUtils.isNoneBlank(query.getUsername())) { + builder.append(" AND u.username_ like '%").append(query.getUsername()).append("%'"); + } + //用户ID + if (StringUtils.isNoneBlank(query.getUserId())) { + builder.append(" AND u.id_ = '").append(query.getUserId()).append("%'"); + } + //账户名称 + if (StringUtils.isNoneBlank(query.getAccount())) { + builder.append(" AND a.account_ like '%").append(query.getAccount()).append("%'"); + } + + //应用id + if (StringUtils.isNoneBlank(query.getAppId())) { + builder.append(" AND a.app_id = '").append(query.getAppId()).append("%'"); + } + + //应用名称 + if(StringUtils.isNotBlank(query.getAppName())){ + builder.append(" AND p.name_ like '%").append(query.getAppName()).append("%'"); + } + //@formatter:on + String sql = builder.toString(); + List list = jdbcTemplate + .query( + builder.append(" LIMIT ").append(pageable.getPageNumber() * pageable.getPageSize()) + .append(",").append(pageable.getPageSize()).toString(), + new AppAccountPoMapper()); + //@formatter:off + String countSql = "SELECT count(*) FROM (" + sql + ") app_account_"; + //@formatter:on + Integer count = jdbcTemplate.queryForObject(countSql, Integer.class); + return new PageImpl<>(list, pageable, count); + } + + /** + * JdbcTemplate + */ + private final JdbcTemplate jdbcTemplate; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppOidcConfigRepositoryCustomizedImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppOidcConfigRepositoryCustomizedImpl.java new file mode 100644 index 00000000..a53eb3f6 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppOidcConfigRepositoryCustomizedImpl.java @@ -0,0 +1,95 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app.impl; + +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.app.po.AppOidcConfigPO; +import cn.topiam.employee.common.repository.app.AppOidcConfigRepositoryCustomized; +import cn.topiam.employee.common.repository.app.impl.mapper.AppOidcConfigPoMapper; + +import lombok.AllArgsConstructor; +import static cn.topiam.employee.common.constants.ProtocolConstants.OIDC_CONFIG_CACHE_NAME; + +/** + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/23 20:51 + */ +@Repository +@AllArgsConstructor +@CacheConfig(cacheNames = { OIDC_CONFIG_CACHE_NAME }) +public class AppOidcConfigRepositoryCustomizedImpl implements AppOidcConfigRepositoryCustomized { + private final String SELECT_SQL = "SELECT aoc.*,app.init_login_url,app.init_login_type,app.authorization_type,app.template_,app.code_,app.is_enabled,app.client_id,app.client_secret from app left join app_oidc_config aoc on app.id_ = aoc.app_id where 1=1"; + + /** + * 根据应用ID获取 + * + * @param appId {@link Long} + * @return {@link AppOidcConfigPO} + */ + @Override + @Cacheable(key = "#p0", unless = "#result==null") + public AppOidcConfigPO getByAppId(Long appId) { + //@formatter:off + String sql =SELECT_SQL+ " AND app_id = " + appId; + //@formatter:on + return jdbcTemplate.queryForObject(sql, new AppOidcConfigPoMapper()); + } + + @Override + @Cacheable(key = "#p0", unless = "#result==null") + public AppOidcConfigPO getByClientId(String clientId) { + //@formatter:off + try { + String sql = SELECT_SQL+ " AND app.client_id = " + "'"+clientId+"'"; + return jdbcTemplate.queryForObject(sql, new AppOidcConfigPoMapper()); + } catch (EmptyResultDataAccessException e){ + return null; + } + //@formatter:on + } + + /** + * 根据应用编码查询应用配置 + * + * @param appCode {@link String} + * @return {@link AppOidcConfigPO} + */ + @Override + @Cacheable(key = "#p0", unless = "#result==null") + public AppOidcConfigPO findByAppCode(String appCode) { + //@formatter:off + String sql = SELECT_SQL + " AND app.code_ = " + "'"+appCode+"'"; + //@formatter:on + try { + return jdbcTemplate.queryForObject(sql, new AppOidcConfigPoMapper()); + } catch (EmptyResultDataAccessException e) { + return null; + } + } + + /** + * JdbcTemplate + */ + private final JdbcTemplate jdbcTemplate; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppPermissionPolicyRepositoryCustomizedImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppPermissionPolicyRepositoryCustomizedImpl.java new file mode 100644 index 00000000..dc91773e --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppPermissionPolicyRepositoryCustomizedImpl.java @@ -0,0 +1,130 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app.impl; + +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import org.springframework.util.ObjectUtils; + +import com.google.common.collect.Lists; + +import cn.topiam.employee.common.entity.app.po.AppPermissionPolicyPO; +import cn.topiam.employee.common.entity.app.query.AppPolicyQuery; +import cn.topiam.employee.common.repository.app.AppPermissionPolicyRepositoryCustomized; +import cn.topiam.employee.common.repository.app.impl.mapper.AppPermissionPolicyPoMapper; + +import lombok.RequiredArgsConstructor; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2021/11/4 22:46 + */ +@Repository +@RequiredArgsConstructor +public class AppPermissionPolicyRepositoryCustomizedImpl implements + AppPermissionPolicyRepositoryCustomized { + + private String leftJoin(String table, String condition) { + return " LEFT JOIN " + table + " ON " + condition; + } + + @Override + public Page findPage(AppPolicyQuery query, Pageable pageable) { + //查询条件 + //@formatter:off + // 所属应用 + StringBuilder where = new StringBuilder("WHERE policy.app_id = '").append(query.getAppId()).append("' "); + // 主体类型 + where.append(" AND policy.subject_type = '").append(query.getSubjectType().getCode()).append("' "); + // 客体类型 + where.append(" AND policy.object_type = '").append(query.getObjectType().getCode()).append("' "); + // 主体id + if (!ObjectUtils.isEmpty(query.getSubjectId())) { + where.append("policy.subject_id = '").append(query.getSubjectId()).append("' "); + } + // 客体id + if (!ObjectUtils.isEmpty(query.getObjectId())) { + where.append("policy.object_id = '").append(query.getObjectId()).append("' "); + } + // 授权效果 + if (!ObjectUtils.isEmpty(query.getEffect())) { + where.append("policy.effect = '").append(query.getEffect().getCode()).append("' "); + } + + List fields = Lists.newArrayList("policy.subject_id", "policy.object_id", "policy.subject_type", "policy.object_type", "policy.id", "policy.effect"); + String subjectJoin; + String objectJoin = null; + switch (query.getSubjectType()) { + case USER -> { + subjectJoin = leftJoin("app_account account", "policy.subject_id = account.id"); + fields.add("account.account as subject_name"); + } + case USER_GROUP -> { + subjectJoin = leftJoin("user_group group", "policy.subject_id = group.id"); + fields.add("group.name as subject_name"); + } + case ORGANIZATION -> { + subjectJoin = leftJoin("organization org", "policy.subject_id = org.id"); + fields.add("org.name as subject_name"); + } + case ROLE -> { + subjectJoin = leftJoin("app_permission_role role", "policy.subject_id = role.id"); + fields.add("role.name as subject_name"); + } + default -> throw new RuntimeException("暂不支持"); + } + switch (query.getObjectType()) { + case PERMISSION -> { + objectJoin = leftJoin("app_permission_action action", "policy.subject_id = action.id"); + fields.add("action.name as object_name"); + } + case ROLE -> { + objectJoin = leftJoin("app_permission_role role2", "policy.subject_id = role2.id"); + fields.add("role2.name as object_name"); + } + case RESOURCE -> { + objectJoin = leftJoin("app_permission_resource resource", "policy.subject_id = resource.id"); + fields.add("resource.name as object_name"); + } + } + StringBuilder selectSql = new StringBuilder("SELECT ").append(String.join(", ", fields)) + .append(" FROM app_permission_policy policy ").append(subjectJoin).append(objectJoin); + + // @formatter:off + List list = jdbcTemplate + .query( + selectSql.append(" LIMIT ").append(pageable.getPageNumber() * pageable.getPageSize()) + .append(",").append(pageable.getPageSize()).toString(), + new AppPermissionPolicyPoMapper()); + //@formatter:off + String countSql = "SELECT count(*) FROM (" + selectSql + ") app_policy_"; + //@formatter:on + Integer count = jdbcTemplate.queryForObject(countSql, Integer.class); + return new PageImpl<>(list, pageable, count); + } + + /** + * JdbcTemplate + */ + private final JdbcTemplate jdbcTemplate; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppRepositoryCustomizedImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppRepositoryCustomizedImpl.java new file mode 100644 index 00000000..2831ffa0 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppRepositoryCustomizedImpl.java @@ -0,0 +1,131 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app.impl; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import com.google.common.collect.Lists; + +import cn.topiam.employee.common.entity.account.OrganizationMemberEntity; +import cn.topiam.employee.common.entity.account.UserGroupMemberEntity; +import cn.topiam.employee.common.entity.app.AppEntity; +import cn.topiam.employee.common.repository.account.OrganizationMemberRepository; +import cn.topiam.employee.common.repository.account.UserGroupMemberRepository; +import cn.topiam.employee.common.repository.app.AppRepositoryCustomized; +import cn.topiam.employee.common.repository.app.impl.mapper.AppEntityMapper; + +import lombok.AllArgsConstructor; + +/** + * App Repository Customized + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/29 20:27 + */ +@Repository +@AllArgsConstructor +public class AppRepositoryCustomizedImpl implements AppRepositoryCustomized { + + /** + * 更新应用状态 + * + * @param id {@link Long} + * @param enabled {@link Boolean} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Integer updateAppStatus(Long id, Boolean enabled) { + StringBuilder builder = new StringBuilder("UPDATE app SET is_enabled=?where id_=?"); + //@formatter:on + return jdbcTemplate.queryForObject(builder.toString(), Integer.class, enabled, id); + } + + /** + * 获取我的应用列表 + * + * @param name {@link String} + * @param userId {@link Long} + * @param pageable {@link String} + * @return {@link List} + */ + @Override + public Page getAppList(Long userId, String name, Pageable pageable) { + List paramList = Lists.newArrayList(); + //当前用户加入的用户组Id + List groupIdList = userGroupMemberRepository.findByUserId(userId).stream() + .map(UserGroupMemberEntity::getGroupId).toList(); + //当前用户加入的组织id + List orgId = organizationMemberRepository.findAllByUserId(userId).stream() + .map(OrganizationMemberEntity::getOrgId).toList(); + paramList.addAll(groupIdList); + paramList.addAll(orgId); + paramList.add(userId); + Map paramMap = new HashMap<>(16); + paramMap.put("subjectIds", paramList); + //@formatter:off + StringBuilder builder = new StringBuilder("SELECT distinct app.* from app LEFT JOIN app_access_policy app_acce ON app.id_ = app_acce.app_id WHERE app_acce.subject_id in (:subjectIds) "); + //用户名 + if (StringUtils.isNoneBlank(name)) { + builder.append(" AND app.name_ like '%").append(name).append("%'"); + } + //@formatter:on + String sql = builder.toString(); + List list = namedParameterJdbcTemplate.query( + builder.append(" LIMIT ").append(pageable.getPageNumber() * pageable.getPageSize()) + .append(",").append(pageable.getPageSize()).toString(), + paramMap, new AppEntityMapper()); + //@formatter:off + String countSql = "SELECT count(*) FROM (" + sql + ") app_account_"; + //@formatter:on + Integer count = namedParameterJdbcTemplate.queryForObject(countSql, paramMap, + Integer.class); + return new PageImpl<>(list, pageable, count); + } + + /** + * JdbcTemplate + */ + private final JdbcTemplate jdbcTemplate; + + /** + * NamedParameterJdbcTemplate + */ + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + /** + * UserGroupMemberRepository + */ + private final UserGroupMemberRepository userGroupMemberRepository; + + /** + * OrganizationMemberRepository + */ + private final OrganizationMemberRepository organizationMemberRepository; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppSaml2ConfigRepositoryCustomizedImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppSaml2ConfigRepositoryCustomizedImpl.java new file mode 100644 index 00000000..2e9355cf --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppSaml2ConfigRepositoryCustomizedImpl.java @@ -0,0 +1,71 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app.impl; + +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.constants.ProtocolConstants; +import cn.topiam.employee.common.entity.app.po.AppSaml2ConfigPO; +import cn.topiam.employee.common.repository.app.AppSaml2ConfigRepositoryCustomized; +import cn.topiam.employee.common.repository.app.impl.mapper.AppSaml2ConfigPoMapper; + +import lombok.AllArgsConstructor; + +/** + * + * @author TopIAM + * Created by support@topiam.cn on 2022/8/23 20:51 + */ +@Repository +@AllArgsConstructor +@CacheConfig(cacheNames = { ProtocolConstants.SAML2_CONFIG_CACHE_NAME }) +public class AppSaml2ConfigRepositoryCustomizedImpl implements AppSaml2ConfigRepositoryCustomized { + /** + * 根据应用ID获取 + * + * @param appId {@link Long} + * @return {@link AppSaml2ConfigPO} + */ + @Override + @Cacheable(key = "#p0", unless = "#result==null") + public AppSaml2ConfigPO getByAppId(Long appId) { + //@formatter:off + String sql = "select as2c.*,app.init_login_url,app.init_login_type,app.authorization_type,app.template_,app.code_,client_id,client_secret from app left join app_saml2_config as2c on app.id_ = as2c.app_id where 1=1" + + " AND app_id = " + appId; + //@formatter:on + return jdbcTemplate.queryForObject(sql, new AppSaml2ConfigPoMapper()); + } + + @Override + @Cacheable(key = "#p0", unless = "#result==null") + public AppSaml2ConfigPO findByAppCode(String appCode) { + //@formatter:off + String sql = "select as2c.*,app.init_login_url,app.init_login_type,app.authorization_type,app.template_,app.code_,client_id,client_secret from app left join app_saml2_config as2c on app.id_ = as2c.app_id where 1=1" + + " AND code_ = " + "'"+appCode+"'"; + //@formatter:on + return jdbcTemplate.queryForObject(sql, new AppSaml2ConfigPoMapper()); + } + + /** + * JdbcTemplate + */ + private final JdbcTemplate jdbcTemplate; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/ResourceRepositoryCustomizedImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/ResourceRepositoryCustomizedImpl.java new file mode 100644 index 00000000..f8676754 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/ResourceRepositoryCustomizedImpl.java @@ -0,0 +1,38 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app.impl; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.repository.authorization.ResourceRepositoryCustomized; + +import lombok.AllArgsConstructor; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/10 22:08 + */ +@Repository +@AllArgsConstructor +public class ResourceRepositoryCustomizedImpl implements ResourceRepositoryCustomized { + /** + * JdbcTemplate + */ + private final JdbcTemplate jdbcTemplate; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/RoleRepositoryCustomizedImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/RoleRepositoryCustomizedImpl.java new file mode 100644 index 00000000..965d2c07 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/RoleRepositoryCustomizedImpl.java @@ -0,0 +1,40 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app.impl; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.repository.authorization.RoleRepositoryCustomized; + +import lombok.AllArgsConstructor; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/10 22:08 + */ +@Repository +@AllArgsConstructor +public class RoleRepositoryCustomizedImpl implements RoleRepositoryCustomized { + + /** + * JdbcTemplate + */ + private final JdbcTemplate jdbcTemplate; + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppAccessPolicyPoMapper.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppAccessPolicyPoMapper.java new file mode 100644 index 00000000..43d74baf --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppAccessPolicyPoMapper.java @@ -0,0 +1,64 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app.impl.mapper; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDateTime; + +import org.springframework.jdbc.core.RowMapper; + +import cn.topiam.employee.common.entity.app.po.AppAccessPolicyPO; +import cn.topiam.employee.common.enums.PolicySubjectType; +import cn.topiam.employee.common.enums.app.AppProtocol; +import cn.topiam.employee.common.enums.app.AppType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/2/13 22:25 + */ +public class AppAccessPolicyPoMapper implements RowMapper { + + /** + * Implementations must implement this method to map each row of data + * in the ResultSet. This method should not call {@code next()} on + * the ResultSet; it is only supposed to map values of the current row. + * + * @param rs the ResultSet to map (pre-initialized for the current row) + * @param rowNum the number of the current row + * @return the result object for the current row (may be {@code null}) + * @throws SQLException if an SQLException is encountered getting + * column values (that is, there's no need to catch SQLException) + */ + @SuppressWarnings("DuplicatedCode") + @Override + public AppAccessPolicyPO mapRow(ResultSet rs, int rowNum) throws SQLException { + AppAccessPolicyPO appAccount = new AppAccessPolicyPO(); + appAccount.setId(rs.getLong("id_")); + appAccount.setAppId(rs.getLong("app_id")); + appAccount.setSubjectId(rs.getString("subject_id")); + appAccount.setSubjectType(PolicySubjectType.getType(rs.getString("subject_type"))); + appAccount.setSubjectName(rs.getString("name_")); + appAccount.setCreateTime(rs.getObject("create_time", LocalDateTime.class)); + appAccount.setAppName(rs.getString("app_name")); + appAccount.setAppType(AppType.getType(rs.getString("app_type"))); + appAccount.setAppTemplate(rs.getString("app_template")); + appAccount.setAppProtocol(AppProtocol.getType(rs.getString("app_protocol"))); + return appAccount; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppAccountPoMapper.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppAccountPoMapper.java new file mode 100644 index 00000000..2b5f2546 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppAccountPoMapper.java @@ -0,0 +1,62 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app.impl.mapper; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDateTime; + +import org.springframework.jdbc.core.RowMapper; + +import cn.topiam.employee.common.entity.app.po.AppAccountPO; +import cn.topiam.employee.common.enums.app.AppProtocol; +import cn.topiam.employee.common.enums.app.AppType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/2/13 22:25 + */ +public class AppAccountPoMapper implements RowMapper { + + /** + * Implementations must implement this method to map each row of data + * in the ResultSet. This method should not call {@code next()} on + * the ResultSet; it is only supposed to map values of the current row. + * + * @param rs the ResultSet to map (pre-initialized for the current row) + * @param rowNum the number of the current row + * @return the result object for the current row (may be {@code null}) + * @throws SQLException if an SQLException is encountered getting + * column values (that is, there's no need to catch SQLException) + */ + @Override + public AppAccountPO mapRow(ResultSet rs, int rowNum) throws SQLException { + AppAccountPO appAccount = new AppAccountPO(); + appAccount.setId(rs.getLong("id_")); + appAccount.setAppId(rs.getLong("app_id")); + appAccount.setUserId(rs.getLong("user_id")); + appAccount.setAccount(rs.getString("account_")); + appAccount.setUsername(rs.getString("username_")); + appAccount.setCreateTime(rs.getObject("create_time", LocalDateTime.class)); + appAccount.setAppName(rs.getString("app_name")); + appAccount.setAppType(AppType.getType(rs.getString("app_type"))); + appAccount.setAppTemplate(rs.getString("app_template")); + appAccount.setAppProtocol(AppProtocol.getType(rs.getString("app_protocol"))); + return appAccount; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppEntityMapper.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppEntityMapper.java new file mode 100644 index 00000000..073e3712 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppEntityMapper.java @@ -0,0 +1,68 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app.impl.mapper; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.springframework.jdbc.core.RowMapper; + +import cn.topiam.employee.common.entity.app.AppEntity; +import cn.topiam.employee.common.enums.app.AppProtocol; +import cn.topiam.employee.common.enums.app.AppType; +import cn.topiam.employee.common.enums.app.AuthorizationType; +import cn.topiam.employee.common.enums.app.InitLoginType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/2/13 22:25 + */ +public class AppEntityMapper implements RowMapper { + + /** + * Implementations must implement this method to map each row of data + * in the ResultSet. This method should not call {@code next()} on + * the ResultSet; it is only supposed to map values of the current row. + * + * @param rs the ResultSet to map (pre-initialized for the current row) + * @param rowNum the number of the current row + * @return the result object for the current row (may be {@code null}) + * @throws SQLException if an SQLException is encountered getting + * column values (that is, there's no need to catch SQLException) + */ + @Override + public AppEntity mapRow(ResultSet rs, int rowNum) throws SQLException { + AppEntity appEntity = new AppEntity(); + appEntity.setId(rs.getLong("id_")); + appEntity.setCode(rs.getString("code_")); + appEntity.setName(rs.getString("name_")); + appEntity.setClientId(rs.getString("client_id")); + appEntity.setClientSecret(rs.getString("client_secret")); + appEntity.setTemplate(rs.getString("template_")); + appEntity.setProtocol(AppProtocol.getType(rs.getString("protocol_"))); + appEntity.setType(AppType.getType(rs.getString("type_"))); + appEntity.setIcon(rs.getString("icon_")); + appEntity.setInitLoginType(InitLoginType.getType(rs.getString("init_login_type"))); + appEntity.setInitLoginUrl(rs.getString("init_login_url")); + appEntity + .setAuthorizationType(AuthorizationType.getType(rs.getString("authorization_type"))); + appEntity.setEnabled(rs.getBoolean("is_enabled")); + appEntity.setRemark(rs.getString("remark_")); + return appEntity; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppOidcConfigPoMapper.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppOidcConfigPoMapper.java new file mode 100644 index 00000000..8eefe006 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppOidcConfigPoMapper.java @@ -0,0 +1,91 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app.impl.mapper; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDateTime; +import java.util.Set; + +import org.springframework.jdbc.core.RowMapper; + +import com.alibaba.fastjson2.JSONObject; + +import cn.topiam.employee.common.entity.app.po.AppOidcConfigPO; +import cn.topiam.employee.common.enums.app.AuthorizationType; +import cn.topiam.employee.common.enums.app.InitLoginType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/2/13 22:25 + */ +@SuppressWarnings({ "DuplicatedCode", "unchecked" }) +public class AppOidcConfigPoMapper implements RowMapper { + + /** + * Implementations must implement this method to map each row of data + * in the ResultSet. This method should not call {@code next()} on + * the ResultSet; it is only supposed to map values of the current row. + * + * @param rs the ResultSet to map (pre-initialized for the current row) + * @param rowNum the number of the current row + * @return the result object for the current row (may be {@code null}) + * @throws SQLException if an SQLException is encountered getting + * column values (that is, there's no need to catch SQLException) + */ + @Override + public AppOidcConfigPO mapRow(ResultSet rs, int rowNum) throws SQLException { + //@formatter:off + AppOidcConfigPO appAccount = new AppOidcConfigPO(); + appAccount.setAppId(rs.getLong("id_")); + appAccount.setAppId(rs.getLong("app_id")); + //应用表相关 + appAccount.setAppCode(rs.getString("code_")); + appAccount.setAppTemplate(rs.getString("template_")); + appAccount.setEnabled(rs.getBoolean("is_enabled")); + appAccount.setClientId(rs.getString("client_id")); + appAccount.setClientSecret(rs.getString("client_secret")); + appAccount.setInitLoginType(InitLoginType.getType(rs.getString("init_login_type"))); + appAccount.setInitLoginUrl(rs.getString("init_login_url")); + appAccount.setAuthorizationType(AuthorizationType.getType(rs.getString("authorization_type"))); + //配置相关 + appAccount.setClientAuthMethods(JSONObject.parseObject(rs.getString("client_auth_methods"), Set.class)); + appAccount.setAuthGrantTypes(JSONObject.parseObject(rs.getString("auth_grant_types"), Set.class)); + appAccount.setResponseTypes(JSONObject.parseObject(rs.getString("response_types"), Set.class)); + appAccount.setRedirectUris(JSONObject.parseObject(rs.getString("redirect_uris"), Set.class)); + appAccount.setGrantScopes(JSONObject.parseObject(rs.getString("grant_scopes"), Set.class)); + appAccount.setRequireAuthConsent(rs.getBoolean("require_auth_consent")); + appAccount.setRequireProofKey(rs.getBoolean("require_proof_key")); + appAccount.setTokenEndpointAuthSigningAlgorithm( + rs.getString("token_endpoint_auth_signing_algorithm")); + appAccount.setRefreshTokenTimeToLive(rs.getInt("refresh_token_time_to_live")); + appAccount.setAccessTokenFormat(rs.getString("access_token_format")); + appAccount.setAccessTokenTimeToLive(rs.getInt("access_token_time_to_live")); + appAccount.setIdTokenTimeToLive(rs.getInt("id_token_time_to_live")); + appAccount.setIdTokenSignatureAlgorithm(rs.getString("id_token_signature_algorithm")); + appAccount.setReuseRefreshToken(rs.getBoolean("reuse_refresh_token")); + //创建修改相关 + appAccount.setCreateBy(rs.getString("create_by")); + appAccount.setCreateTime(rs.getObject("create_time", LocalDateTime.class)); + appAccount.setUpdateBy(rs.getString("update_by")); + appAccount.setCreateTime(rs.getObject("update_time", LocalDateTime.class)); + appAccount.setRemark(rs.getString("remark_")); + return appAccount; + //@formatter:on + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppPermissionPolicyPoMapper.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppPermissionPolicyPoMapper.java new file mode 100644 index 00000000..0f59940a --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppPermissionPolicyPoMapper.java @@ -0,0 +1,62 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app.impl.mapper; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.springframework.jdbc.core.RowMapper; + +import cn.topiam.employee.common.entity.app.po.AppPermissionPolicyPO; +import cn.topiam.employee.common.enums.PolicyEffect; +import cn.topiam.employee.common.enums.PolicyObjectType; +import cn.topiam.employee.common.enums.PolicySubjectType; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/2/13 23:25 + */ +public class AppPermissionPolicyPoMapper implements RowMapper { + + /** + * Implementations must implement this method to map each row of data + * in the ResultSet. This method should not call {@code next()} on + * the ResultSet; it is only supposed to map values of the current row. + * + * @param rs the ResultSet to map (pre-initialized for the current row) + * @param rowNum the number of the current row + * @return the result object for the current row (may be {@code null}) + * @throws SQLException if an SQLException is encountered getting + * column values (that is, there's no need to catch SQLException) + */ + @SuppressWarnings("DuplicatedCode") + @Override + public AppPermissionPolicyPO mapRow(ResultSet rs, int rowNum) throws SQLException { + AppPermissionPolicyPO appPermissionPolicyPo = new AppPermissionPolicyPO(); + appPermissionPolicyPo.setId(rs.getLong("id_")); + appPermissionPolicyPo.setEffect(PolicyEffect.getType(rs.getString("effect"))); + appPermissionPolicyPo.setSubjectId(rs.getString("subject_id")); + appPermissionPolicyPo + .setSubjectType(PolicySubjectType.getType(rs.getString("subject_type"))); + appPermissionPolicyPo.setSubjectName(rs.getString("subject_name")); + appPermissionPolicyPo.setObjectId(rs.getLong("object_id")); + appPermissionPolicyPo.setObjectType(PolicyObjectType.getType(rs.getString("object_type"))); + appPermissionPolicyPo.setObjectName(rs.getString("object_name")); + return appPermissionPolicyPo; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppSaml2ConfigPoMapper.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppSaml2ConfigPoMapper.java new file mode 100644 index 00000000..aab982c3 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppSaml2ConfigPoMapper.java @@ -0,0 +1,128 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app.impl.mapper; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDateTime; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.jdbc.core.RowMapper; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; + +import cn.topiam.employee.common.entity.app.AppSaml2ConfigEntity; +import cn.topiam.employee.common.entity.app.po.AppSaml2ConfigPO; +import cn.topiam.employee.common.enums.app.*; +import cn.topiam.employee.support.exception.TopIamException; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/2/13 22:25 + */ +@SuppressWarnings("DuplicatedCode") +public class AppSaml2ConfigPoMapper implements RowMapper { + + /** + * Implementations must implement this method to map each row of data + * in the ResultSet. This method should not call {@code next()} on + * the ResultSet; it is only supposed to map values of the current row. + * + * @param rs the ResultSet to map (pre-initialized for the current row) + * @param rowNum the number of the current row + * @return the result object for the current row (may be {@code null}) + * @throws SQLException if an SQLException is encountered getting + * column values (that is, there's no need to catch SQLException) + */ + @Override + public AppSaml2ConfigPO mapRow(ResultSet rs, int rowNum) throws SQLException { + AppSaml2ConfigPO appAccount = new AppSaml2ConfigPO(); + appAccount.setAppId(rs.getLong("id_")); + appAccount.setAppId(rs.getLong("app_id")); + appAccount.setAppCode(rs.getString("code_")); + appAccount.setClientId(rs.getString("client_id")); + appAccount.setClientSecret(rs.getString("client_secret")); + appAccount.setSpMetadata(rs.getString("sp_metadata")); + appAccount.setSpEntityId(rs.getString("sp_entity_id")); + appAccount.setSpAcsUrl(rs.getString("sp_acs_url")); + appAccount.setRecipient(rs.getString("recipient_")); + appAccount.setAudience(rs.getString("audience_")); + appAccount.setSpSloUrl(rs.getString("sp_slo_url")); + appAccount.setSpRequestsSigned(rs.getBoolean("sp_requests_signed")); + appAccount.setSpSignCert(rs.getString("sp_sign_cert")); + appAccount.setAcsBinding(rs.getString("acs_binding")); + appAccount.setSlsBinding(rs.getString("sls_binding")); + appAccount.setNameIdFormat(SamlNameIdFormatType.getType(rs.getString("nameid_format"))); + appAccount + .setNameIdValueType(SamlNameIdValueType.getType(rs.getString("nameid_value_type"))); + //加密断言 + appAccount.setAssertSigned(rs.getBoolean("assert_signed")); + appAccount.setAssertSignAlgorithm( + SamlSignAssertAlgorithmType.getType(rs.getString("assert_sign_algorithm"))); + //签名断言 + appAccount.setAssertEncrypted(rs.getBoolean("assert_encrypted")); + appAccount.setAssertEncryptAlgorithm( + SamlEncryptAssertAlgorithmType.getType(rs.getString("assert_encrypt_algorithm"))); + //响应签名 + appAccount.setResponseSigned(rs.getBoolean("response_signed")); + appAccount.setResponseSignAlgorithm( + SamlSignAssertAlgorithmType.getType(rs.getString("response_sign_algorithm"))); + + appAccount.setAuthnContextClassRef( + AuthnContextClassRefType.getType(rs.getString("authn_context_classref"))); + appAccount.setRelayState(rs.getString("relay_state")); + appAccount.setInitLoginType(InitLoginType.getType(rs.getString("init_login_type"))); + appAccount.setInitLoginUrl(rs.getString("init_login_url")); + appAccount.setAppTemplate(rs.getString("template_")); + appAccount + .setAuthorizationType(AuthorizationType.getType(rs.getString("authorization_type"))); + try { + //属性语句 + String attributeStatements = rs.getString("attribute_statements"); + if (StringUtils.isNotBlank(attributeStatements)) { + JavaType attributeStatementType = objectMapper.getTypeFactory() + .constructCollectionType(List.class, + AppSaml2ConfigEntity.AttributeStatement.class); + List attributeStatement = objectMapper + .readValue(rs.getString("attribute_statements"), attributeStatementType); + appAccount.setAttributeStatements(attributeStatement); + } + + //额外参数 + String additionalConfig = rs.getString("additional_config"); + if (StringUtils.isNotBlank(additionalConfig)) { + appAccount.setAdditionalConfig(objectMapper + .readValue(rs.getString("additional_config"), new TypeReference<>() { + })); + } + } catch (Exception e) { + throw new TopIamException(e.getMessage()); + } + appAccount.setCreateBy(rs.getString("create_by")); + appAccount.setCreateTime(rs.getObject("create_time", LocalDateTime.class)); + appAccount.setUpdateBy(rs.getString("update_by")); + appAccount.setCreateTime(rs.getObject("update_time", LocalDateTime.class)); + appAccount.setRemark(rs.getString("remark_")); + return appAccount; + } + + private final ObjectMapper objectMapper = new ObjectMapper(); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/package-info.java new file mode 100644 index 00000000..44157282 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.app; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/authentication/IdentityProviderRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/authentication/IdentityProviderRepository.java new file mode 100644 index 00000000..9d474867 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/authentication/IdentityProviderRepository.java @@ -0,0 +1,130 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.authentication; + +import java.util.List; +import java.util.Optional; + +import org.jetbrains.annotations.NotNull; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity; +import cn.topiam.employee.common.enums.IdentityProviderType; + +/** + *

+ * 身份认证源配置 Repository 接口 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-16 + */ +@Repository +@CacheConfig(cacheNames = "idp") +public interface IdentityProviderRepository extends CrudRepository, + PagingAndSortingRepository, + QuerydslPredicateExecutor { + /** + * 根据平台类型查询认证源配置 + * + * @param type {@link IdentityProviderType} + * @return {@link IdentityProviderEntity} + */ + List findByType(IdentityProviderType type); + + /** + * 根据平台类型查询是否显示 + * + * @return {@link List} + */ + List findByEnabledIsTrueAndDisplayedIsTrue(); + + /** + * 查询启用的社交认证源 + * + * @return {@link List} + */ + List findByEnabledIsTrue(); + + /** + * save + * + * @param entity {@link S} + * @param {@link S} + * @return {@link IdentityProviderEntity} + */ + @NotNull + @Override + @CacheEvict(allEntries = true) + S save(@NotNull S entity); + + /** + * Deletes the entity with the given id. + * + * @param id must not be {@literal null}. + * @throws IllegalArgumentException in case the given {@literal id} is {@literal null} + */ + @Override + @CacheEvict(allEntries = true) + void deleteById(@NotNull Long id); + + /** + * Retrieves an entity by its id. + * + * @param id must not be {@literal null}. + * @return the entity with the given id or {@literal Optional#empty()} if none found. + * @throws IllegalArgumentException if {@literal id} is {@literal null}. + */ + @NotNull + @Override + @Cacheable(key = "#a0") + Optional findById(@NotNull Long id); + + /** + * 更新社交认证源状态 + * + * @param id {@link Long} + * @param enabled {@link Boolean} + * @return {@link Boolean} + */ + @Transactional(rollbackFor = Exception.class) + @Modifying + @CacheEvict(allEntries = true) + @Query(value = "UPDATE IdentityProviderEntity SET enabled=:enabled where id=:id") + Integer updateIdentityProviderStatus(@Param(value = "id") Long id, + @Param(value = "enabled") Boolean enabled); + + /** + * 根据ID查找,并且为启用 + * + * @param id {@link Long} + * @return {@link IdentityProviderEntity} + */ + @Cacheable(key = "#a0", unless = "#result == null") + Optional findByIdAndEnabledIsTrue(Long id); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/authorization/ResourceRepositoryCustomized.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/authorization/ResourceRepositoryCustomized.java new file mode 100644 index 00000000..e9c73d57 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/authorization/ResourceRepositoryCustomized.java @@ -0,0 +1,25 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.authorization; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/29 20:48 + */ +public interface ResourceRepositoryCustomized { +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/authorization/RoleRepositoryCustomized.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/authorization/RoleRepositoryCustomized.java new file mode 100644 index 00000000..dd5260ce --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/authorization/RoleRepositoryCustomized.java @@ -0,0 +1,26 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.authorization; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/10 22:32 + */ +public interface RoleRepositoryCustomized { + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceEventRecordRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceEventRecordRepository.java new file mode 100644 index 00000000..2ac58618 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceEventRecordRepository.java @@ -0,0 +1,37 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.identitysource; + +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.identitysource.IdentitySourceEventRecordEntity; + +/** + * 身份源事件记录 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/3/15 20:35 + */ +@Repository +public interface IdentitySourceEventRecordRepository extends + PagingAndSortingRepository, + QuerydslPredicateExecutor, + IdentitySourceEventRecordRepositoryCustomized { +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceEventRecordRepositoryCustomized.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceEventRecordRepositoryCustomized.java new file mode 100644 index 00000000..872872d1 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceEventRecordRepositoryCustomized.java @@ -0,0 +1,37 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.identitysource; + +import java.util.List; + +import cn.topiam.employee.common.entity.identitysource.IdentitySourceEventRecordEntity; + +/** + * 身份源事件记录 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/3/15 20:35 + */ +public interface IdentitySourceEventRecordRepositoryCustomized { + /** + * 批量保存 + * + * @param list {@link List} + */ + void batchSave(List list); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceRepository.java new file mode 100644 index 00000000..75c7cfaa --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceRepository.java @@ -0,0 +1,129 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.identitysource; + +import java.util.List; +import java.util.Optional; + +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.common.constants.AccountConstants; +import cn.topiam.employee.common.entity.identitysource.IdentitySourceEntity; + +/** + *

+ * 身份认证源配置 Repository 接口 + *

+ * 部分操作使用了缓存,后期更改使用,请务必注意! + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-16 + */ +@Repository +@CacheConfig(cacheNames = { AccountConstants.IDS_CACHE_NAME }) +public interface IdentitySourceRepository extends CrudRepository, + PagingAndSortingRepository, + QuerydslPredicateExecutor { + /** + * 根据ID查询 + * + * @param id {@link Long} + * @return {@link IdentitySourceEntity} + */ + @Override + @Cacheable(key = "#p0", unless = "#result==null") + Optional findById(Long id); + + /** + * 查询启用的身份源 + * + * @return {@link List} + */ + @Cacheable + List findByEnabledIsTrue(); + + /** + * 更新身份源状态 + * + * @param id {@link Long} + * @param enabled {@link Boolean} + * @return {@link Boolean} + */ + @Transactional(rollbackFor = Exception.class) + @Modifying + @CacheEvict(allEntries = true) + @Query(value = "UPDATE IdentitySourceEntity SET enabled=:enabled where id=:id") + Integer updateIdentitySourceStatus(@Param(value = "id") Long id, + @Param(value = "enabled") Boolean enabled); + + /** + * Saves a given entity. Use the returned instance for further operations as the save operation might have changed the + * entity instance completely. + * + * @param entity must not be {@literal null}. + * @return the saved entity; will never be {@literal null}. + * @throws IllegalArgumentException in case the given {@literal entity} is {@literal null}. + */ + @Override + @NonNull + @CacheEvict(allEntries = true) + S save(@NonNull S entity); + + /** + * Deletes the entity with the given id. + * + * @param id must not be {@literal null}. + * @throws IllegalArgumentException in case the given {@literal id} is {@literal null} + */ + @Override + @CacheEvict(allEntries = true) + void deleteById(@NonNull Long id); + + /** + * 更新设分院策略 + * + * @param id {@link Long} 主键 + * @param strategyConfig {@link String} 策略 + */ + @Transactional(rollbackFor = Exception.class) + @Modifying + @CacheEvict(allEntries = true) + @Query(value = "UPDATE IdentitySourceEntity SET strategyConfig=:strategyConfig where id=:id") + void updateStrategyConfig(@Param(value = "id") Long id, + @Param(value = "strategyConfig") String strategyConfig); + + /** + * 根据code查询 + * + * @param code {@link String} + * @return {@link IdentitySourceEntity} + */ + Optional findByCode(String code); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceSyncHistoryRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceSyncHistoryRepository.java new file mode 100644 index 00000000..79245cbf --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceSyncHistoryRepository.java @@ -0,0 +1,38 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.identitysource; + +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.identitysource.IdentitySourceSyncHistoryEntity; + +/** + * 身份源同步结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/3/15 20:35 + */ +@Repository +public interface IdentitySourceSyncHistoryRepository extends + CrudRepository, + PagingAndSortingRepository, + QuerydslPredicateExecutor { +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceSyncRecordRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceSyncRecordRepository.java new file mode 100644 index 00000000..ebf89475 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceSyncRecordRepository.java @@ -0,0 +1,37 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.identitysource; + +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.identitysource.IdentitySourceSyncRecordEntity; + +/** + * 身份源同步详情 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/3/15 20:35 + */ +@Repository +public interface IdentitySourceSyncRecordRepository extends + PagingAndSortingRepository, + QuerydslPredicateExecutor, + IdentitySourceSyncRecordRepositoryCustomized { +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceSyncRecordRepositoryCustomized.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceSyncRecordRepositoryCustomized.java new file mode 100644 index 00000000..8e01fb8e --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/IdentitySourceSyncRecordRepositoryCustomized.java @@ -0,0 +1,37 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.identitysource; + +import java.util.List; + +import cn.topiam.employee.common.entity.identitysource.IdentitySourceSyncRecordEntity; + +/** + * 身份源同步记录 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/3/15 20:35 + */ +public interface IdentitySourceSyncRecordRepositoryCustomized { + /** + * 批量保存 + * + * @param list {@link List} + */ + void batchSave(List list); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/impl/IdentitySourceEventRecordRepositoryCustomizedImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/impl/IdentitySourceEventRecordRepositoryCustomizedImpl.java new file mode 100644 index 00000000..f705697c --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/impl/IdentitySourceEventRecordRepositoryCustomizedImpl.java @@ -0,0 +1,83 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.identitysource.impl; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.List; +import java.util.Objects; + +import org.jetbrains.annotations.NotNull; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.identitysource.IdentitySourceEventRecordEntity; +import cn.topiam.employee.common.repository.identitysource.IdentitySourceEventRecordRepositoryCustomized; + +/** + * + * @author TopIAM + * Created by support@topiam.cn on 2022/10/10 23:33 + */ +@SuppressWarnings("DuplicatedCode") +@Repository +public class IdentitySourceEventRecordRepositoryCustomizedImpl implements + IdentitySourceEventRecordRepositoryCustomized { + + @Override + public void batchSave(List list) { + jdbcTemplate.batchUpdate( + "INSERT INTO identity_source_event_record (id_, identity_source_id, action_type, object_id, object_name, object_type, status_,event_time,desc_,create_by,create_time,update_by,update_time,remark_) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)", + new BatchPreparedStatementSetter() { + @Override + public void setValues(@NotNull PreparedStatement ps, int i) throws SQLException { + //@formatter:off + IdentitySourceEventRecordEntity entity = list.get(i); + ps.setLong(1, entity.getId()); + ps.setLong(2, entity.getIdentitySourceId()); + ps.setString(3, Objects.isNull(entity.getActionType()) ? null : entity.getActionType().getCode()); + ps.setString(4, entity.getObjectId()); + ps.setString(5, entity.getObjectName()); + ps.setString(6, Objects.isNull(entity.getObjectType()) ? null : entity.getObjectType().getCode()); + ps.setString(7, Objects.isNull(entity.getStatus()) ? null : entity.getStatus().getCode()); + ps.setTimestamp(8, Timestamp.valueOf(entity.getEventTime())); + ps.setString(9, entity.getDesc()); + ps.setString(10, entity.getCreateBy()); + ps.setTimestamp(11, Timestamp.valueOf(entity.getCreateTime())); + ps.setString(12, entity.getUpdateBy()); + ps.setTimestamp(13, Timestamp.valueOf(entity.getUpdateTime())); + ps.setString(14, entity.getRemark()); + //@formatter:on + } + + @Override + public int getBatchSize() { + return list.size(); + } + }); + } + + private final JdbcTemplate jdbcTemplate; + + public IdentitySourceEventRecordRepositoryCustomizedImpl(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/impl/IdentitySourceSyncRecordRepositoryCustomizedImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/impl/IdentitySourceSyncRecordRepositoryCustomizedImpl.java new file mode 100644 index 00000000..c5a9d0b8 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/identitysource/impl/IdentitySourceSyncRecordRepositoryCustomizedImpl.java @@ -0,0 +1,81 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.identitysource.impl; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.List; +import java.util.Objects; + +import org.jetbrains.annotations.NotNull; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.identitysource.IdentitySourceSyncRecordEntity; +import cn.topiam.employee.common.repository.identitysource.IdentitySourceSyncRecordRepositoryCustomized; + +/** + * 身份源同步记录 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/3/15 20:35 + */ +@Repository +public class IdentitySourceSyncRecordRepositoryCustomizedImpl implements + IdentitySourceSyncRecordRepositoryCustomized { + + @Override + public void batchSave(List list) { + jdbcTemplate.batchUpdate( + "INSERT INTO identity_source_sync_record (id_, sync_history_id, action_type, object_id, object_name, object_type, status_,desc_,create_by,create_time,update_by,update_time,remark_) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", + new BatchPreparedStatementSetter() { + @Override + public void setValues(@NotNull PreparedStatement ps, int i) throws SQLException { + //@formatter:off + IdentitySourceSyncRecordEntity entity = list.get(i); + ps.setLong(1, entity.getId()); + ps.setLong(2, entity.getSyncHistoryId()); + ps.setString(3, Objects.isNull(entity.getActionType()) ? null : entity.getActionType().getCode()); + ps.setString(4, entity.getObjectId()); + ps.setString(5, entity.getObjectName()); + ps.setString(6, Objects.isNull(entity.getObjectType()) ? null : entity.getObjectType().getCode()); + ps.setString(7, Objects.isNull(entity.getStatus()) ? null : entity.getStatus().getCode()); + ps.setString(8, entity.getDesc()); + ps.setString(9, entity.getCreateBy()); + ps.setTimestamp(10, Timestamp.valueOf(entity.getCreateTime())); + ps.setString(11, entity.getUpdateBy()); + ps.setTimestamp(12, Timestamp.valueOf(entity.getUpdateTime())); + ps.setString(13, entity.getRemark()); + //@formatter:on + } + + @Override + public int getBatchSize() { + return list.size(); + } + }); + } + + private final JdbcTemplate jdbcTemplate; + + public IdentitySourceSyncRecordRepositoryCustomizedImpl(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/package-info.java new file mode 100644 index 00000000..4564b023 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/setting/AdministratorRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/setting/AdministratorRepository.java new file mode 100644 index 00000000..badb996a --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/setting/AdministratorRepository.java @@ -0,0 +1,133 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.setting; + +import java.util.Optional; + +import org.jetbrains.annotations.NotNull; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.common.entity.setting.AdministratorEntity; +import static cn.topiam.employee.common.constants.SettingConstants.ADMIN_CACHE_NAME; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2021/11/13 22:09 + */ +@Repository +@CacheConfig(cacheNames = { ADMIN_CACHE_NAME }) +public interface AdministratorRepository extends CrudRepository, + QuerydslPredicateExecutor { + + /** + * findById + * + * @param id must not be {@literal null}. + * @return {@link AdministratorEntity} + */ + @NotNull + @Override + @Cacheable(key = "#p0", unless = "#result==null") + Optional findById(@NotNull Long id); + + /** + * findById + * + * @param id must not be {@literal null}. + */ + @Override + @CacheEvict(allEntries = true) + void deleteById(@NotNull Long id); + + /** + * findById + * + * @param ids must not be {@literal null}. + */ + @Override + @CacheEvict(allEntries = true) + void deleteAllById(@NotNull Iterable ids); + + /** + * save + * + * @param entity must not be {@literal null}. + * @return {@link AdministratorEntity} + * @param + */ + @Override + @CacheEvict(allEntries = true) + S save(@NotNull S entity); + + /** + * 根据用户名查询 + * + * @param username {@link String} + * @return {@link AdministratorEntity} + */ + Optional findByUsername(String username); + + /** + * 根据手机号查询 + * + * @param phone {@link String} + * @return {@link AdministratorEntity} + */ + Optional findByPhone(String phone); + + /** + * 根据邮箱查询 + * + * @param email {@link String} + * @return {@link AdministratorEntity} + */ + Optional findByEmail(String email); + + /** + * 更新管理员状态 + * + * @param id {@link String} + * @param status {@link String} + */ + @Transactional(rollbackFor = Exception.class) + @Modifying + @CacheEvict(allEntries = true) + @Query(value = "update administrator set status_ = ?2 where id_ = ?1", nativeQuery = true) + void updateStatus(@Param(value = "id") String id, @Param(value = "status") String status); + + /** + * 更新代码 + * + * @param id {@link String} + * @param password {@link String} + */ + @Transactional(rollbackFor = Exception.class) + @Modifying + @CacheEvict(allEntries = true) + @Query(value = "update administrator set password_ = ?2 where id_ = ?1", nativeQuery = true) + void updatePassword(@Param(value = "id") String id, @Param(value = "password") String password); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/setting/MailTemplateRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/setting/MailTemplateRepository.java new file mode 100644 index 00000000..017a625e --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/setting/MailTemplateRepository.java @@ -0,0 +1,53 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.setting; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.common.entity.setting.MailTemplateEntity; +import cn.topiam.employee.common.enums.MailType; + +/** + *

+ * 邮件模板 Repository 接口 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-13 + */ +@Repository +public interface MailTemplateRepository extends CrudRepository { + /** + * 根据类型查询模板 + * + * @param type {@link MailType} + * @return {@link MailTemplateEntity} + */ + MailTemplateEntity findByType(@Param("type") MailType type); + + /** + * 根据模块类型删除模块 + * + * @param type {@link MailType} + */ + @Transactional(rollbackFor = Exception.class) + void deleteByType(@Param("type") MailType type); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/setting/SettingRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/setting/SettingRepository.java new file mode 100644 index 00000000..bef45d55 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/setting/SettingRepository.java @@ -0,0 +1,106 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.repository.setting; + +import java.util.List; +import java.util.Objects; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.common.entity.setting.SettingEntity; +import cn.topiam.employee.support.util.BeanUtils; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_BY; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_TIME; + +/** + * 设置表 Repository 接口 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/5 22:09 + */ +@Repository +public interface SettingRepository extends CrudRepository { + /** + * 根据KEY查询 + * + * @param name {@link String} + * @return {@link SettingEntity} + */ + SettingEntity findByName(String name); + + /** + * 根据KEY查询 + * + * @param name {@link String} + * @return {@link SettingEntity} + */ + List findByNameLike(String name); + + /** + * 根据类型查询安全配置 + * + * @param names {@link List} + * @return {@link SettingEntity} + */ + List findByNameIn(List names); + + /** + * 根据名称查询是否存在 + * + * @param name {@link String} + * @return {@link Boolean} + */ + Boolean existsByName(String name); + + /** + * 根据名称删除 + * + * @param name {@link String} + */ + @Transactional(rollbackFor = Exception.class) + void deleteByName(String name); + + /** + * 根据名称列表删除 + * + * @param names {@link String} + */ + @Transactional(rollbackFor = Exception.class) + void deleteByNameIn(List names); + + /** + * 保存配置 + * + * @param list {@link List} + * @return {@link Boolean} + */ + default Boolean saveConfig(List list) { + for (SettingEntity setting : list) { + SettingEntity type = findByName(setting.getName()); + if (Objects.isNull(type)) { + save(setting); + continue; + } + BeanUtils.merge(setting, type, LAST_MODIFIED_BY, LAST_MODIFIED_TIME); + save(type); + } + return true; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/storage/AbstractStorage.java b/eiam-common/src/main/java/cn/topiam/employee/common/storage/AbstractStorage.java new file mode 100644 index 00000000..495238cb --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/storage/AbstractStorage.java @@ -0,0 +1,65 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.storage; + +import java.util.UUID; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2021/11/10 20:36 + */ +public class AbstractStorage implements Storage { + protected StorageConfig config; + public static final String SEPARATOR = "/"; + public static final String JOINER = "-"; + public static final int EXPIRY_SECONDS = 3600; + + public AbstractStorage(StorageConfig config) { + this.config = config; + } + + /** + * 判断域名是否为https + * + * @param url + * @return + */ + public boolean getUrlSecure(String url) { + return "https:".equals(url.split("//")[0]); + } + + public String getFileName(String fileName, MultipartFile file) { + if (StringUtils.isBlank(fileName)) { + fileName = file.getOriginalFilename(); + } + return UUID.randomUUID().toString().replace(JOINER, "").toLowerCase() + JOINER + fileName; + } + + @Override + public String upload(String fileName, MultipartFile file) throws StorageProviderException { + return null; + } + + @Override + public String download(String path) throws StorageProviderException { + return null; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/storage/Storage.java b/eiam-common/src/main/java/cn/topiam/employee/common/storage/Storage.java new file mode 100644 index 00000000..5b49c5ef --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/storage/Storage.java @@ -0,0 +1,47 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.storage; + +import org.springframework.web.multipart.MultipartFile; + +/** + * 存储 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/10 20:29 + */ +public interface Storage { + /** + * 上传文件 + * + * @param fileName {@link String} + * @param file {@link MultipartFile} + * @return path + * @throws Exception Exception + */ + String upload(String fileName, MultipartFile file) throws Exception; + + /** + * 下载文件 + * + * @param path {@link String} + * @return path {@link String} + * @throws Exception Exception + */ + String download(String path) throws Exception; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/storage/StorageConfig.java b/eiam-common/src/main/java/cn/topiam/employee/common/storage/StorageConfig.java new file mode 100644 index 00000000..1193f06b --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/storage/StorageConfig.java @@ -0,0 +1,74 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.storage; + +import javax.validation.constraints.NotEmpty; + +import cn.topiam.employee.common.storage.enums.StorageProvider; + +import lombok.Builder; +import lombok.Data; + +/** + * 存储配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/10/1 19:10 + */ +@Data +@Builder +public class StorageConfig { + public StorageConfig() { + } + + public StorageConfig(StorageProvider provider) { + this.provider = provider; + } + + public StorageConfig(StorageProvider provider, Config config) { + this.provider = provider; + this.config = config; + } + + /** + * 平台 + */ + @NotEmpty(message = "平台类型不能为空") + private StorageProvider provider; + + /** + * 配置 + */ + private Config config; + + /** + * Config + */ + @Data + public static class Config { + /** + * 域名 + */ + @NotEmpty(message = "访问域名不能为空") + private String domain; + /** + * 存储位置 + */ + private String location; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/storage/StorageFactory.java b/eiam-common/src/main/java/cn/topiam/employee/common/storage/StorageFactory.java new file mode 100644 index 00000000..3f0b90ea --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/storage/StorageFactory.java @@ -0,0 +1,48 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.storage; + +import java.lang.reflect.Constructor; + +/** + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/10 20:40 + */ +public class StorageFactory { + + private StorageFactory() { + } + + /** + * 获取实例化 + * + * @param config {@link StorageConfig} + * @return {@link Storage} + */ + @SuppressWarnings("JavaReflectionInvocation") + public static Storage getStorage(StorageConfig config) { + try { + Constructor constructor = config.getProvider().getStorage() + .getDeclaredConstructor(StorageConfig.class); + return constructor.newInstance(config); + } catch (Exception e) { + throw new StorageProviderException(e.getMessage(), e); + } + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/storage/StorageProviderException.java b/eiam-common/src/main/java/cn/topiam/employee/common/storage/StorageProviderException.java new file mode 100644 index 00000000..e9e1af5d --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/storage/StorageProviderException.java @@ -0,0 +1,56 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.storage; + +import java.io.Serial; + +import org.springframework.http.HttpStatus; + +import cn.topiam.employee.support.exception.TopIamException; + +/** + * 存储服务异常 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/19 22:53 + */ +public class StorageProviderException extends TopIamException { + @Serial + private static final long serialVersionUID = 6249098979022610064L; + + public StorageProviderException(String msg, Throwable t) { + super(msg, t); + } + + public StorageProviderException(String msg) { + super(msg); + } + + public StorageProviderException(String msg, HttpStatus status) { + super(msg, status); + } + + public StorageProviderException(String error, String description, HttpStatus status) { + super(error, description, status); + } + + public StorageProviderException(Throwable cause, String error, String description, + HttpStatus status) { + super(cause, error, description, status); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/storage/controller/StorageFileResource.java b/eiam-common/src/main/java/cn/topiam/employee/common/storage/controller/StorageFileResource.java new file mode 100644 index 00000000..ff3e29bc --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/storage/controller/StorageFileResource.java @@ -0,0 +1,90 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.storage.controller; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import cn.topiam.employee.common.constants.StorageConstants; +import cn.topiam.employee.common.storage.Storage; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 存储配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/10/30 21:16 + */ +@Validated +@Tag(name = "存储文件") +@RestController +@RequestMapping(value = StorageConstants.STORAGE_PATH, produces = MediaType.APPLICATION_JSON_VALUE) +@AllArgsConstructor +@Slf4j +public class StorageFileResource { + + /** + * 上传文件 + * + * @return {@link ApiRestResult} + */ + @Operation(summary = "上传文件") + @PostMapping(value = "/upload") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN) or hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).USER)") + public ApiRestResult uploadFile(@Schema(description = "文件名") String fileName, + @Schema(description = "文件") MultipartFile file) { + try { + return ApiRestResult.ok(storage.upload(fileName, file)); + } catch (Exception e) { + log.error("Failed to upload storage file: {}", e.getMessage(), e); + return ApiRestResult.err(e.getMessage()); + } + } + + /** + * 获取存储文件路径 + * + * @return {@link ApiRestResult} + */ + @Operation(summary = "获取存储文件路径") + @GetMapping(value = "/get") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN) or hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).USER)") + public ApiRestResult getUrl(@Schema(description = "文件路径") @RequestParam String path) { + try { + return ApiRestResult.ok(storage.download(path)); + } catch (Exception e) { + log.error("Failed to get storage file: {}", e.getMessage(), e); + return ApiRestResult.err(e.getMessage()); + } + } + + /** + * Storage + */ + private final Storage storage; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/storage/enums/StorageProvider.java b/eiam-common/src/main/java/cn/topiam/employee/common/storage/enums/StorageProvider.java new file mode 100644 index 00000000..afb92cc5 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/storage/enums/StorageProvider.java @@ -0,0 +1,107 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.storage.enums; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonValue; + +import cn.topiam.employee.common.storage.Storage; +import cn.topiam.employee.common.storage.impl.*; +import cn.topiam.employee.support.web.converter.EnumConvert; + +/** + * 短信平台 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/19 + */ +public enum StorageProvider implements Serializable { + + /** + * 阿里云 + */ + ALIYUN_OSS("aliyun_oss", "阿里云OSS", + AliYunOssStorage.class), + /** + * 腾讯云 + */ + TENCENT_COS("tencent_cos", "腾讯云COS", + TencentCosStorage.class), + /** + * 七牛 + */ + QINIU_KODO("qiniu_kodo", "七牛云Kodo", + QiNiuKodoStorage.class), + /** + * 本地存储 + */ + LOCAL("local", "本地存储", LocalStorage.class), + /** + * minio + */ + MINIO("minio", "minio", MinIoStorage.class); + + /** + * code + */ + @JsonValue + private final String code; + /** + * desc + */ + private final String desc; + /** + * Storage + */ + private final Class storage; + + StorageProvider(String code, String desc, Class storage) { + this.code = code; + this.desc = desc; + this.storage = storage; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + public Class getStorage() { + return storage; + } + + @EnumConvert + public static StorageProvider getType(String code) { + StorageProvider[] values = values(); + for (StorageProvider status : values) { + if (String.valueOf(status.getCode()).equals(code)) { + return status; + } + } + throw new NullPointerException("未找到该平台"); + } + + @Override + public String toString() { + return this.code; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/storage/enums/convert/StorageProviderConverter.java b/eiam-common/src/main/java/cn/topiam/employee/common/storage/enums/convert/StorageProviderConverter.java new file mode 100644 index 00000000..b7e1a53e --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/storage/enums/convert/StorageProviderConverter.java @@ -0,0 +1,69 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.storage.enums.convert; + +import java.util.Objects; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import cn.topiam.employee.common.storage.enums.StorageProvider; + +/** + * SmsPlatformWritingConverter + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/5 21:52 + */ +@Converter(autoApply = true) +public class StorageProviderConverter implements AttributeConverter { + + /** + * Converts the value stored in the entity attribute into the + * data representation to be stored in the database. + * + * @param attribute the entity attribute value to be converted + * @return the converted data to be stored in the database + * column + */ + @Override + public String convertToDatabaseColumn(StorageProvider attribute) { + if (Objects.isNull(attribute)) { + return null; + } + return attribute.getCode(); + } + + /** + * Converts the data stored in the database column into the + * value to be stored in the entity attribute. + * Note that it is the responsibility of the converter writer to + * specify the correct dbData type for the corresponding + * column for use by the JDBC driver: i.e., persistence providers are + * not expected to do such type conversion. + * + * @param dbData the data from the database column to be + * converted + * @return the converted value to be stored in the entity + * attribute + */ + @Override + public StorageProvider convertToEntityAttribute(String dbData) { + return StorageProvider.getType(dbData); + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/AliYunOssStorage.java b/eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/AliYunOssStorage.java new file mode 100644 index 00000000..a98c6b76 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/AliYunOssStorage.java @@ -0,0 +1,145 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.storage.impl; + +import java.net.URL; +import java.util.Date; + +import javax.validation.constraints.NotEmpty; + +import org.apache.commons.lang3.time.DateUtils; +import org.springframework.web.multipart.MultipartFile; + +import com.aliyun.oss.HttpMethod; +import com.aliyun.oss.OSS; +import com.aliyun.oss.OSSClientBuilder; +import com.aliyun.oss.model.CreateBucketRequest; +import com.aliyun.oss.model.GeneratePresignedUrlRequest; +import com.aliyun.oss.model.PutObjectRequest; + +import cn.topiam.employee.common.storage.AbstractStorage; +import cn.topiam.employee.common.storage.StorageConfig; +import cn.topiam.employee.common.storage.StorageProviderException; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; + +/** + * 阿里云OSS + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/10 20:28 + */ +@Slf4j +public class AliYunOssStorage extends AbstractStorage { + + private final Config aliYunConfig; + private final OSS ossClient; + + public AliYunOssStorage(StorageConfig config) { + super(config); + aliYunConfig = (Config) config.getConfig(); + // 创建OSSClient实例。 + ossClient = new OSSClientBuilder().build(aliYunConfig.getEndpoint(), + aliYunConfig.getAccessKeyId(), aliYunConfig.getAccessKeySecret()); + createBucket(); + } + + private void createBucket() { + // 判断存储空间examplebucket是否存在。如果返回值为true,则存储空间存在,否则存储空间不存在。 + boolean exists = ossClient.doesBucketExist(aliYunConfig.getBucket()); + if (!exists) { + // 创建CreateBucketRequest对象。 + CreateBucketRequest createBucketRequest = new CreateBucketRequest( + aliYunConfig.getBucket()); + // 创建存储空间。 + ossClient.createBucket(createBucketRequest); + } + } + + @Override + public String upload(String fileName, MultipartFile file) throws StorageProviderException { + try { + super.upload(fileName, file); + String key = aliYunConfig.getLocation() + SEPARATOR + getFileName(fileName, file); + // 创建PutObjectRequest对象。 + // 依次填写Bucket名称(例如examplebucket)和Object完整路径(例如exampledir/exampleobject.txt)。Object完整路径中不能包含Bucket名称。 + PutObjectRequest putObjectRequest = new PutObjectRequest(aliYunConfig.getBucket(), key, + file.getInputStream()); + // 上传字符串 + ossClient.putObject(putObjectRequest); + return aliYunConfig.getDomain() + SEPARATOR + aliYunConfig.getBucket() + SEPARATOR + + key; + } catch (Exception e) { + log.error("ali yun upload exception: {}", e.getMessage(), e); + throw new StorageProviderException("ali yun upload exception", e); + } + } + + @Override + public String download(String path) throws StorageProviderException { + super.download(path); + /** + * 所有OSS支持的请求和各种Header参数,在URL中进行签名的算法和在Header中包含签名的算法基本一样。 + * 生成URL中的签名字符串时,除了将Date参数替换为Expires参数外,仍然包含CONTENT-TYPE、CONTENT-MD5、CanonicalizedOSSHeaders等在Header中包含签名中定义的Header(请求中虽然仍有Date请求Header,但无需将Date加入签名字符串中)。 + * 在URL中包含签名时必须对URL进行urlencode。如果在URL中多次传入Signature、Expires或OSSAccessKeyId,则以第一次传入的值为准。 + * 使用URL签名时,OSS会先验证请求时间是否晚于Expires时间,然后再验证签名。 + * urlencode(base64(hmac-sha1(AccessKeySecret, + * VERB + "\n" + * + CONTENT-MD5 + "\n" + * + CONTENT-TYPE + "\n" + * + EXPIRES + "\n" + * + CanonicalizedOSSHeaders + * + CanonicalizedResource))) + */ + GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest( + aliYunConfig.getBucket(), path, HttpMethod.GET); + request.setExpiration(DateUtils.addSeconds(new Date(), EXPIRY_SECONDS)); + URL url = ossClient.generatePresignedUrl(request); + return url.toString(); + } + + /** + * 阿里云OSS配置 + */ + @EqualsAndHashCode(callSuper = true) + @Data + public static class Config extends StorageConfig.Config { + /** + * accessKeyId + */ + @NotEmpty(message = "AccessKeyId不能为空") + private String accessKeyId; + /** + * accessKeySecret + */ + @NotEmpty(message = "AccessKeySecret不能为空") + private String accessKeySecret; + /** + * endpoint + */ + @NotEmpty(message = "Endpoint不能为空") + private String endpoint; + /** + * bucket + */ + @NotEmpty(message = "Bucket不能为空") + private String bucket; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/LocalStorage.java b/eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/LocalStorage.java new file mode 100644 index 00000000..0b27e167 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/LocalStorage.java @@ -0,0 +1,59 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.storage.impl; + +import org.springframework.web.multipart.MultipartFile; + +import cn.topiam.employee.common.storage.AbstractStorage; +import cn.topiam.employee.common.storage.StorageConfig; +import cn.topiam.employee.common.storage.StorageProviderException; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 本地存储配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/10 20:32 + */ +public class LocalStorage extends AbstractStorage { + + public LocalStorage(StorageConfig config) { + super(config); + } + + @Override + public String upload(String fileName, MultipartFile file) throws StorageProviderException { + return super.upload(fileName, file); + } + + @Override + public String download(String path) throws StorageProviderException { + return super.download(path); + } + + /** + * 本地存储 + */ + @EqualsAndHashCode(callSuper = true) + @Data + public static class Config extends StorageConfig.Config { + + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/MinIoStorage.java b/eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/MinIoStorage.java new file mode 100644 index 00000000..99b9c476 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/MinIoStorage.java @@ -0,0 +1,134 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.storage.impl; + +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.validation.constraints.NotEmpty; + +import org.springframework.web.multipart.MultipartFile; + +import cn.topiam.employee.common.storage.AbstractStorage; +import cn.topiam.employee.common.storage.StorageConfig; +import cn.topiam.employee.common.storage.StorageProviderException; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; + +import io.minio.*; +import io.minio.errors.*; +import io.minio.http.Method; + +/** + * minio + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/10 20:32 + */ +@Slf4j +public class MinIoStorage extends AbstractStorage { + + private final MinioClient minioClient; + private final Config minioConfig; + + public MinIoStorage(StorageConfig config) { + super(config); + try { + minioConfig = (Config) this.config.getConfig(); + this.minioClient = MinioClient.builder().endpoint(minioConfig.getEndpoint()) + .credentials(minioConfig.getAccessKey(), minioConfig.getSecretKey()).build(); + createBucket(this.minioClient, minioConfig); + } catch (Exception e) { + log.error("create bucket excception: {}", e.getMessage(), e); + throw new StorageProviderException("create bucket excception", e); + } + } + + private void createBucket(MinioClient minioClient, + Config minioConfig) throws ServerException, InsufficientDataException, + ErrorResponseException, IOException, + NoSuchAlgorithmException, InvalidKeyException, + InvalidResponseException, XmlParserException, + InternalException { + boolean found = minioClient + .bucketExists(BucketExistsArgs.builder().bucket(minioConfig.getBucket()).build()); + if (!found) { + log.warn("{} does not exist", minioConfig.getBucket()); + minioClient + .makeBucket(MakeBucketArgs.builder().bucket(minioConfig.getBucket()).build()); + } + } + + @Override + public String upload(String fileName, MultipartFile file) throws StorageProviderException { + try { + super.upload(fileName, file); + String key = this.minioConfig.getLocation() + SEPARATOR + getFileName(fileName, file); + this.minioClient.putObject(PutObjectArgs.builder().bucket(this.minioConfig.getBucket()) + .object(key).stream(file.getInputStream(), file.getSize(), 5 * 1024 * 1024) + .contentType(file.getContentType()).build()); + return this.minioConfig.getDomain() + SEPARATOR + this.minioConfig.getBucket() + + SEPARATOR + minioConfig.getBucket() + SEPARATOR + key; + } catch (Exception e) { + log.error("minio download exception: {}", e.getMessage(), e); + throw new StorageProviderException("minio upload exception", e); + } + } + + @Override + public String download(String path) throws StorageProviderException { + try { + super.download(path); + String downloadUrl = this.minioClient.getPresignedObjectUrl( + GetPresignedObjectUrlArgs.builder().bucket(minioConfig.getBucket()).object(path) + .method(Method.GET).expiry(EXPIRY_SECONDS).build()); + return downloadUrl.replace(minioConfig.getEndpoint(), minioConfig.getDomain()); + } catch (Exception e) { + log.error("minio download exception: {}", e.getMessage(), e); + throw new StorageProviderException("minio upload exception", e); + } + } + + @EqualsAndHashCode(callSuper = true) + @Data + public static class Config extends StorageConfig.Config { + /** + * AccessKey + */ + @NotEmpty(message = "AccessKey不能为空") + private String accessKey; + /** + * SecretKey + */ + @NotEmpty(message = "SecretKey不能为空") + private String secretKey; + /** + * endpoint + */ + @NotEmpty(message = "Endpoint不能为空") + private String endpoint; + /** + * bucket + */ + @NotEmpty(message = "Bucket不能为空") + private String bucket; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/QiNiuKodoStorage.java b/eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/QiNiuKodoStorage.java new file mode 100644 index 00000000..dac186a6 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/QiNiuKodoStorage.java @@ -0,0 +1,155 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.storage.impl; + +import javax.validation.constraints.NotEmpty; + +import org.springframework.web.multipart.MultipartFile; + +import com.alibaba.fastjson2.JSON; +import com.qiniu.common.QiniuException; +import com.qiniu.http.Response; +import com.qiniu.storage.Configuration; +import com.qiniu.storage.DownloadUrl; +import com.qiniu.storage.Region; +import com.qiniu.storage.UploadManager; +import com.qiniu.storage.model.DefaultPutRet; +import com.qiniu.util.Auth; + +import cn.topiam.employee.common.storage.AbstractStorage; +import cn.topiam.employee.common.storage.StorageConfig; +import cn.topiam.employee.common.storage.StorageProviderException; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; + +/** + * 七牛kodo + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/10 20:33 + */ +@Slf4j +public class QiNiuKodoStorage extends AbstractStorage { + + private final UploadManager uploadManager; + private final Config qiNiuConfig; + + public QiNiuKodoStorage(StorageConfig config) { + super(config); + qiNiuConfig = (Config) this.config.getConfig(); + Configuration cfg = new Configuration(Region.huadong()); + uploadManager = new UploadManager(cfg); + // try { + // createBucket(cfg); + // } catch (Exception e) { + // log.error("create bucket error: {}", e.getMessage(), e); + // } + } + + // private void createBucket(Configuration cfg) { + // try { + // Auth auth = Auth.create(qiNiuConfig.getAccessKey(), qiNiuConfig.getSecretKey()); + // BucketManager bucketManager = new BucketManager(auth, cfg); + // Response response = bucketManager.createBucket(qiNiuConfig.getBucket(), "z0"); + // //解析创建成功的结果 + // BucketInfo putRet = JSON.parseObject(response.bodyString(), BucketInfo.class); + // log.info("qi niu create bucket response: {}", putRet); + // } catch (QiniuException ex) { + // Response r = ex.response; + // log.error("qi niu create bucket fail response: {}", r.toString()); + // try { + // log.error("qi niu create bucket fail response body: {}", r.bodyString()); + // } catch (QiniuException ex2) { + // //ignore + // } + // throw new StorageProviderException("qiu niu create bucket exception", ex); + // } + // } + + @Override + public String upload(String fileName, MultipartFile file) throws StorageProviderException { + try { + super.upload(fileName, file); + Auth auth = Auth.create(qiNiuConfig.getAccessKey(), qiNiuConfig.getSecretKey()); + String upToken = auth.uploadToken(qiNiuConfig.getBucket()); + Response response = uploadManager.put(file.getBytes(), + qiNiuConfig.getLocation() + SEPARATOR + getFileName(fileName, file), upToken, null, + null, true); + //解析上传成功的结果 + DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class); + log.info("qi niu upload response: {}", putRet); + return qiNiuConfig.getDomain() + SEPARATOR + putRet.key; + } catch (QiniuException ex) { + Response r = ex.response; + log.error("qi niu upload fail response: {}", r.toString()); + try { + log.error("qi niu upload fail response body: {}", r.bodyString()); + } catch (QiniuException ex2) { + //ignore + } + throw new StorageProviderException("qiu niu upload exception", ex); + } catch (Exception e) { + throw new StorageProviderException("qiu niu upload exception", e); + } + } + + @Override + public String download(String path) throws StorageProviderException { + try { + super.download(path); + DownloadUrl url = new DownloadUrl(qiNiuConfig.getDomain(), + getUrlSecure(qiNiuConfig.getDomain()), path); + Auth auth = Auth.create(qiNiuConfig.getAccessKey(), qiNiuConfig.getSecretKey()); + // 1小时,可以自定义链接过期时间 + return url.buildURL(auth, EXPIRY_SECONDS); + } catch (QiniuException ex) { + Response r = ex.response; + log.error("qi niu download fail response: {}", r.toString()); + try { + log.error("qi niu download fail response body: {}", r.bodyString()); + } catch (QiniuException ex2) { + //ignore + } + throw new StorageProviderException("qiu niu download exception", ex); + } catch (Exception e) { + throw new StorageProviderException("qiu niu download exception", e); + } + } + + @EqualsAndHashCode(callSuper = true) + @Data + public static class Config extends StorageConfig.Config { + /** + * AccessKey + */ + @NotEmpty(message = "AccessKey不能为空") + private String accessKey; + /** + * SecretKey + */ + @NotEmpty(message = "SecretKey不能为空") + private String secretKey; + /** + * bucket + */ + @NotEmpty(message = "Bucket不能为空") + private String bucket; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/TencentCosStorage.java b/eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/TencentCosStorage.java new file mode 100644 index 00000000..c8362bf9 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/storage/impl/TencentCosStorage.java @@ -0,0 +1,173 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.storage.impl; + +import java.net.URL; +import java.util.Date; + +import javax.validation.constraints.NotEmpty; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DateUtils; +import org.springframework.web.multipart.MultipartFile; + +import com.qcloud.cos.COSClient; +import com.qcloud.cos.ClientConfig; +import com.qcloud.cos.auth.BasicCOSCredentials; +import com.qcloud.cos.auth.COSCredentials; +import com.qcloud.cos.exception.CosClientException; +import com.qcloud.cos.exception.CosServiceException; +import com.qcloud.cos.http.HttpMethodName; +import com.qcloud.cos.http.HttpProtocol; +import com.qcloud.cos.model.*; +import com.qcloud.cos.region.Region; + +import cn.topiam.employee.common.storage.AbstractStorage; +import cn.topiam.employee.common.storage.StorageConfig; +import cn.topiam.employee.common.storage.StorageProviderException; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; + +/** + * 腾讯cos + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/10 20:29 + */ +@Slf4j +public class TencentCosStorage extends AbstractStorage { + private final Config tencentConfig; + private final COSClient cosClient; + + public TencentCosStorage(StorageConfig config) { + super(config); + // 1 初始化用户身份信息(secretId, secretKey)。 + // SECRETID和SECRETKEY请登录访问管理控制台 https://console.cloud.tencent.com/cam/capi 进行查看和管理 + tencentConfig = (Config) this.config.getConfig(); + COSCredentials cred = new BasicCOSCredentials(tencentConfig.getSecretId(), + tencentConfig.getSecretKey()); + // 2 设置 bucket 的地域, COS 地域的简称请参照 https://cloud.tencent.com/document/product/436/6224 + // clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者常见问题 Java SDK 部分。 + ClientConfig clientConfig = new ClientConfig(new Region(tencentConfig.getRegion())); + clientConfig.setHttpProtocol(HttpProtocol.https); + // 3 生成 cos 客户端。 + cosClient = new COSClient(cred, clientConfig); + createBucket(); + } + + private void createBucket() throws StorageProviderException { + HeadBucketResult headBucketResult = cosClient + .headBucket(new HeadBucketRequest(tencentConfig.getBucket())); + if (StringUtils.isEmpty(headBucketResult.getBucketRegion())) { + // 存储桶名称,格式: BucketName-APPID + String bucket = String.join(tencentConfig.getBucket(), JOINER, + tencentConfig.getAppId()); + CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucket); + // 设置 bucket 的权限为 Private(私有读写)、其他可选有 PublicRead(公有读私有写)、PublicReadWrite(公有读写) + createBucketRequest.setCannedAcl(CannedAccessControlList.PublicRead); + try { + cosClient.createBucket(createBucketRequest); + } catch (CosServiceException serverException) { + log.error("tencent create bucket server exception: {}", + serverException.getErrorMessage(), serverException); + } catch (CosClientException clientException) { + log.error("tencent create bucket client exception: {}", + clientException.getMessage(), clientException); + } + throw new StorageProviderException("tencent create bucket exception"); + } + } + + @Override + public String upload(String fileName, MultipartFile file) throws StorageProviderException { + try { + super.upload(fileName, file); + PutObjectRequest putObjectRequest = new PutObjectRequest(tencentConfig.getBucket(), + tencentConfig.getLocation() + SEPARATOR + getFileName(fileName, file), + file.getInputStream(), null); + cosClient.putObject(putObjectRequest); + return tencentConfig.getDomain() + SEPARATOR + tencentConfig.getBucket() + SEPARATOR + + putObjectRequest.getKey(); + } catch (Exception e) { + log.error("tencent upload exception: {}", e.getMessage(), e); + throw new StorageProviderException("tencent upload exception", e); + } + } + + @Override + public String download(String path) throws StorageProviderException { + try { + // 初始化永久密钥信息 + // SECRETID和SECRETKEY请登录访问管理控制台进行查看和管理 + COSCredentials cred = new BasicCOSCredentials(tencentConfig.getSecretId(), + tencentConfig.getSecretKey()); + Region region = new Region("COS_REGION"); + ClientConfig clientConfig = new ClientConfig(region); + // 如果要生成一个使用 https 协议的 URL,则设置此行,推荐设置。 + clientConfig.setHttpProtocol(HttpProtocol.https); + // 生成 cos 客户端。 + COSClient cosClient = new COSClient(cred, clientConfig); + GeneratePresignedUrlRequest req = new GeneratePresignedUrlRequest( + tencentConfig.getBucket(), path, HttpMethodName.GET); + // 设置签名过期时间(可选), 若未进行设置, 则默认使用 ClientConfig 中的签名过期时间(1小时) + // 可以设置任意一个未来的时间,推荐是设置 10 分钟到 3 天的过期时间 + // 这里设置签名在半个小时后过期 + req.setExpiration(DateUtils.addSeconds(new Date(), EXPIRY_SECONDS)); + URL url = cosClient.generatePresignedUrl(req); + return url.toString(); + } catch (Exception e) { + log.error("tencent download exception: {}", e.getMessage(), e); + throw new StorageProviderException("tencent download exception", e); + } + } + + /** + * 腾讯cos配置 + */ + @EqualsAndHashCode(callSuper = true) + @Data + public static class Config extends StorageConfig.Config { + /** + * AppId + */ + @NotEmpty(message = "AppId不能为空") + private String appId; + /** + * secretId + */ + @NotEmpty(message = "SecretId不能为空") + private String secretId; + /** + * SecretKey + */ + @NotEmpty(message = "SecretKey不能为空") + private String secretKey; + /** + * Region + */ + @NotEmpty(message = "Region不能为空") + private String region; + /** + * bucket + */ + @NotEmpty(message = "Bucket不能为空") + private String bucket; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/storage/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/storage/package-info.java new file mode 100644 index 00000000..66bfe8b5 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/storage/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.storage; \ No newline at end of file diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/util/InputStreamMetadataResolver.java b/eiam-common/src/main/java/cn/topiam/employee/common/util/InputStreamMetadataResolver.java new file mode 100644 index 00000000..d93aa7ea --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/util/InputStreamMetadataResolver.java @@ -0,0 +1,75 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.util; + +import java.io.InputStream; + +import javax.annotation.Nonnull; + +import org.opensaml.saml.metadata.resolver.impl.AbstractReloadingMetadataResolver; + +import cn.topiam.employee.support.util.Md5Utils; + +import net.shibboleth.utilities.java.support.resolver.ResolverException; + +/** + * InputStreamMetadataResolver + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/30 23:50 + */ +public class InputStreamMetadataResolver extends AbstractReloadingMetadataResolver { + private InputStream inputStream; + + public InputStream getInputStream() { + return inputStream; + } + + public void setInputStream(InputStream inputStream) { + this.inputStream = inputStream; + } + + public InputStreamMetadataResolver(@Nonnull final InputStream inputStream) { + super(); + setInputStream(inputStream); + } + + /** + * Gets an identifier which may be used to distinguish this metadata in logging statements. + * + * @return identifier which may be used to distinguish this metadata in logging statements + */ + @Override + protected String getMetadataIdentifier() { + return Md5Utils.md516(getInputStream().toString()); + } + + /** + * Fetches metadata from a source. + * + * @return the fetched metadata, or null if the metadata is known not to have changed since the last retrieval + */ + @Override + protected byte[] fetchMetadata() { + try { + return inputstreamToByteArray(inputStream); + } catch (ResolverException e) { + throw new RuntimeException(e); + } + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/util/RequestUtils.java b/eiam-common/src/main/java/cn/topiam/employee/common/util/RequestUtils.java new file mode 100644 index 00000000..68682da1 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/util/RequestUtils.java @@ -0,0 +1,84 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022-03-02 22:57 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class RequestUtils { + /** + **把request转换成json数据 + */ + public static String getBody(HttpServletRequest request) { + StringBuilder sb = new StringBuilder(); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + } catch (IOException e) { + e.printStackTrace(); + } + return sb.toString(); + } + + /** + **把request转换成xml数据 + */ + public static T getXml(HttpServletRequest request, Class clazz) throws JAXBException { + String body = getBody(request); + StringReader reader = new StringReader(body); + return (T) JAXBContext.newInstance(clazz).createUnmarshaller().unmarshal(reader); + } + + /** + **把request转换成map数据 + */ + public static Map getParams(HttpServletRequest request) { + Map params = new HashMap<>(16); + Map requestParams = request.getParameterMap(); + for (Map.Entry entry : requestParams.entrySet()) { + String[] values = entry.getValue(); + String valueStr = ""; + for (int i = 0; i < values.length; i++) { + valueStr = (i == values.length - 1) ? valueStr + values[i] + : valueStr + values[i] + ","; + } + params.put(entry.getKey(), valueStr); + } + return params; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/util/SamlKeyStoreProvider.java b/eiam-common/src/main/java/cn/topiam/employee/common/util/SamlKeyStoreProvider.java new file mode 100644 index 00000000..eeaa18a8 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/util/SamlKeyStoreProvider.java @@ -0,0 +1,172 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.util; + +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.util.*; + +import org.opensaml.core.criterion.EntityIdCriterion; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.opensaml.security.credential.Credential; +import org.opensaml.security.credential.impl.KeyStoreCredentialResolver; +import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap; +import org.opensaml.xmlsec.keyinfo.KeyInfoGenerator; +import org.opensaml.xmlsec.keyinfo.NamedKeyInfoGeneratorManager; +import org.opensaml.xmlsec.signature.KeyInfo; + +import cn.topiam.employee.support.exception.TopIamException; + +import net.shibboleth.utilities.java.support.resolver.CriteriaSet; +import net.shibboleth.utilities.java.support.resolver.ResolverException; +import static org.springframework.util.StringUtils.hasText; + +import static cn.topiam.employee.common.util.SamlUtils.getParserPool; + +/** + * 用于解析证书与私钥的工具类 + * + * @author TopIAM + */ +public class SamlKeyStoreProvider { + + private static final char[] DEFAULT_KS_PASSWD = UUID.randomUUID().toString().toCharArray(); + + /** + * 获取 KeyInfo + * + * @param credential {@link Credential} + * @return {@link KeyInfo} + */ + public static KeyInfo getKeyInfo(Credential credential) { + try { + NamedKeyInfoGeneratorManager manager = DefaultSecurityConfigurationBootstrap + .buildBasicKeyInfoGeneratorManager(); + KeyInfoGenerator generator = Objects + .requireNonNull(manager.getDefaultManager().getFactory(credential)).newInstance(); + return generator.generate(credential); + } catch (Exception e) { + throw new TopIamException(e.getMessage()); + } + } + + public static KeyStore getKeyStore(String entityId, String privateKey, String certificateString, + String password) { + try { + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(null, DEFAULT_KS_PASSWD); + + byte[] certBytes = X509Utilities.getDer(certificateString); + Certificate certificate = X509Utilities.getCertificate(certBytes); + ks.setCertificateEntry(entityId, certificate); + + if (hasText(privateKey)) { + PrivateKey pkey = X509Utilities.readPrivateKey(privateKey, password); + ks.setKeyEntry(entityId, pkey, password.toCharArray(), + new Certificate[] { certificate }); + } + + return ks; + } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException + | IOException e) { + throw new RuntimeException(e); + } + } + + private static KeyStoreCredentialResolver getCredentialsResolver(String entityId, String key, + String cer, String password) { + KeyStore ks = getKeyStore(entityId, key, cer, password); + Map passwords = hasText(key) ? Collections.singletonMap(entityId, password) + : Collections.emptyMap(); + return new KeyStoreCredentialResolver(ks, passwords); + } + + public static Credential getCredential(String entityId, String key, String cer, + String password) { + try { + KeyStoreCredentialResolver resolver = getCredentialsResolver(entityId, key, cer, + password); + CriteriaSet cs = new CriteriaSet(); + EntityIdCriterion criteria = new EntityIdCriterion(entityId); + cs.add(criteria); + return resolver.resolveSingle(cs); + } catch (ResolverException e) { + throw new RuntimeException("Can't obtain private key", e); + } + } + + /** + * 获取 KeyStoreCredentialResolver + * + * @param entityId {@link String} + * @param credential {@link String} + * @return {@link KeyStoreCredentialResolver} + */ + public static KeyStoreCredentialResolver getKeyStoreCredentialResolver(String entityId, + String credential) { + + KeyStore keyStore = getKeyStore(entityId, credential); + return new KeyStoreCredentialResolver(keyStore, new HashMap<>(16)); + } + + public static KeyStore getKeyStore(String entityId, String cert) { + try { + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(null, DEFAULT_KS_PASSWD); + + byte[] certBytes = X509Utilities.getDer(cert); + Certificate certificate = X509Utilities.getCertificate(certBytes); + ks.setCertificateEntry(entityId, certificate); + return ks; + } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException + | IOException e) { + throw new RuntimeException(e); + } + } + + /** + * 获取 EntityDescriptor + * + * @param inputStream {@link InputStream} + * @return {@link EntityDescriptor} + */ + public static List getEntityDescriptors(InputStream inputStream) { + List list = new ArrayList<>(); + try { + InputStreamMetadataResolver idpMetaDataProvider = new InputStreamMetadataResolver( + inputStream); + idpMetaDataProvider.setRequireValidMetadata(true); + idpMetaDataProvider.setId(idpMetaDataProvider.getClass().getCanonicalName()); + idpMetaDataProvider.setParserPool(getParserPool()); + idpMetaDataProvider.initialize(); + for (EntityDescriptor idpEntityDescriptor : idpMetaDataProvider) { + list.add(idpEntityDescriptor); + } + } catch (Exception e) { + throw new TopIamException(e.getMessage(), e); + } + return list; + } + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/util/SamlUtils.java b/eiam-common/src/main/java/cn/topiam/employee/common/util/SamlUtils.java new file mode 100644 index 00000000..571bc387 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/util/SamlUtils.java @@ -0,0 +1,207 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.servlet.http.HttpServletRequest; +import javax.xml.namespace.QName; + +import org.opensaml.core.config.ConfigurationService; +import org.opensaml.core.config.InitializationException; +import org.opensaml.core.config.InitializationService; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.XMLObjectBuilderFactory; +import org.opensaml.core.xml.config.XMLObjectProviderRegistry; +import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; +import org.opensaml.core.xml.io.Marshaller; +import org.opensaml.core.xml.io.MarshallingException; +import org.opensaml.messaging.context.MessageContext; +import org.opensaml.saml.common.SAMLObject; +import org.opensaml.saml.common.SignableSAMLObject; +import org.opensaml.saml.saml2.binding.decoding.impl.HTTPPostDecoder; +import org.opensaml.saml.saml2.binding.decoding.impl.HTTPRedirectDeflateDecoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Element; + +import cn.topiam.employee.support.exception.TopIamException; + +import net.shibboleth.utilities.java.support.component.ComponentInitializationException; +import net.shibboleth.utilities.java.support.security.IdentifierGenerationStrategy; +import net.shibboleth.utilities.java.support.security.impl.SecureRandomIdentifierGenerationStrategy; +import net.shibboleth.utilities.java.support.xml.BasicParserPool; +import net.shibboleth.utilities.java.support.xml.ParserPool; +import net.shibboleth.utilities.java.support.xml.SerializeSupport; +import static org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport.getMarshallerFactory; +import static org.opensaml.saml.common.xml.SAMLConstants.POST_METHOD; + +/** + * SAML utils + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/18 21:54 + */ +public class SamlUtils { + private final static Logger logger = LoggerFactory.getLogger(SamlUtils.class); + private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false); + + /** + * transformSamlObject2String + * + * @param samlObject {@link SAMLObject} + * @return {@link String} + */ + public static String transformSamlObject2String(SAMLObject samlObject) { + try { + return SerializeSupport.nodeToString( + Objects.requireNonNull(getMarshallerFactory().getMarshaller(samlObject)) + .marshall(samlObject)); + } catch (Exception e) { + throw new TopIamException(e.getMessage()); + } + } + + private static final IdentifierGenerationStrategy IDENTIFIER_GENERATION_STRATEGY; + + static { + IDENTIFIER_GENERATION_STRATEGY = new SecureRandomIdentifierGenerationStrategy(); + + } + + public static T buildSamlObject(final Class clazz) { + T object; + try { + XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport + .getBuilderFactory(); + QName defaultElementName = (QName) clazz.getDeclaredField("DEFAULT_ELEMENT_NAME") + .get(null); + object = (T) builderFactory.getBuilder(defaultElementName) + .buildObject(defaultElementName); + } catch (IllegalAccessException | NoSuchFieldException e) { + throw new IllegalArgumentException("Could not create SAML object"); + } + + return object; + } + + public static String generateSecureRandomId() { + return IDENTIFIER_GENERATION_STRATEGY.generateIdentifier(); + } + + public static void logSamlObject(final XMLObject object) { + Element element = null; + + if (object instanceof SignableSAMLObject && ((SignableSAMLObject) object).isSigned() + && object.getDOM() != null) { + element = object.getDOM(); + } else { + try { + Marshaller out = XMLObjectProviderRegistrySupport.getMarshallerFactory() + .getMarshaller(object); + out.marshall(object); + element = object.getDOM(); + + } catch (MarshallingException e) { + logger.error(e.getMessage(), e); + } + } + + String xmlString = SerializeSupport.prettyPrintXML(element); + + logger.info("\n" + xmlString); + + } + + /** + * 初始化openSaml + */ + public static void initOpenSaml() { + if (INITIALIZED.compareAndSet(false, true)) { + logger.trace("Initializing OpenSAML"); + XMLObjectProviderRegistry registry = new XMLObjectProviderRegistry(); + ConfigurationService.register(XMLObjectProviderRegistry.class, registry); + registry.setParserPool(getParserPool()); + try { + InitializationService.initialize(); + logger.debug("Initialized OpenSAML"); + } catch (InitializationException e) { + logger.error(e.getMessage(), e); + } + } + logger.debug("Refused to re-initialize OpenSAML"); + } + + @SuppressWarnings("HttpUrlsUsage") + public static ParserPool getParserPool() { + BasicParserPool parserPool = new BasicParserPool(); + parserPool.setMaxPoolSize(100); + parserPool.setCoalescing(true); + parserPool.setIgnoreComments(true); + parserPool.setIgnoreElementContentWhitespace(true); + parserPool.setNamespaceAware(true); + parserPool.setExpandEntityReferences(false); + parserPool.setXincludeAware(false); + + final Map features = new HashMap<>(16); + features.put("http://xml.org/sax/features/external-general-entities", Boolean.FALSE); + features.put("http://xml.org/sax/features/external-parameter-entities", Boolean.FALSE); + features.put("http://apache.org/xml/features/disallow-doctype-decl", Boolean.TRUE); + features.put("http://apache.org/xml/features/validation/schema/normalized-value", + Boolean.FALSE); + features.put("http://javax.xml.XMLConstants/feature/secure-processing", Boolean.TRUE); + parserPool.setBuilderFeatures(features); + parserPool.setBuilderAttributes(new HashMap<>(16)); + try { + parserPool.initialize(); + } catch (ComponentInitializationException e) { + logger.error(e.getMessage(), e); + } + + return parserPool; + } + + /** + * 获取 Message Context + * + * @param request {@link HttpServletRequest} + * @return {@link MessageContext} + */ + public static MessageContext getMessageContext(HttpServletRequest request) { + try { + if (request.getMethod().equals(POST_METHOD)) { + HTTPPostDecoder decoder = new HTTPPostDecoder(); + decoder.setHttpServletRequest(request); + decoder.initialize(); + decoder.decode(); + return decoder.getMessageContext(); + } + //GET + HTTPRedirectDeflateDecoder decoder = new HTTPRedirectDeflateDecoder(); + decoder.setHttpServletRequest(request); + decoder.initialize(); + decoder.decode(); + return decoder.getMessageContext(); + } catch (Exception e) { + throw new TopIamException(e.getMessage(), e); + } + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/util/X509Utilities.java b/eiam-common/src/main/java/cn/topiam/employee/common/util/X509Utilities.java new file mode 100644 index 00000000..045a7608 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/util/X509Utilities.java @@ -0,0 +1,136 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.util; + +import java.io.ByteArrayInputStream; +import java.io.CharArrayReader; +import java.io.IOException; +import java.security.*; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; + +import javax.xml.bind.DatatypeConverter; + +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.openssl.PEMDecryptorProvider; +import org.bouncycastle.openssl.PEMEncryptedKeyPair; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; + +/** + * 读取证书和私钥的工具类 + * + * @author TopIAM + */ +public class X509Utilities { + + public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----\n"; + public static final String END_CERT = "-----END CERTIFICATE-----"; + public static final String BEGIN_KEY = "-----BEGIN RSA PRIVATE KEY-----\n"; + public static final String END_KEY = "-----END RSA PRIVATE KEY-----"; + + public static byte[] getDer(String combinedKeyAndCertPem, String begin, String end) { + String[] tokens = combinedKeyAndCertPem.split(begin); + tokens = tokens[0].split(end); + return getDer(tokens[0]); + } + + public static byte[] getDer(String pem) { + String data = keyCleanup(pem); + + return DatatypeConverter.parseBase64Binary(data); + } + + public static String keyCleanup(String pem) { + return pem.replace(BEGIN_CERT, "").replace(END_CERT, "").replace(BEGIN_KEY, "") + .replace(END_KEY, "").replace("\n", "").trim(); + } + + public static X509Certificate getCertificate(byte[] der) throws CertificateException { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(der)); + } + + public static RSAPrivateKey getPrivateKey(byte[] der, + String algorithm) throws InvalidKeySpecException, + NoSuchAlgorithmException { + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(der); + KeyFactory factory = KeyFactory.getInstance(algorithm); + return (RSAPrivateKey) factory.generatePrivate(spec); + } + + public static PrivateKey readPrivateKey(String pem, String passphrase) throws IOException { + + PEMParser parser = new PEMParser(new CharArrayReader(pem.toCharArray())); + Object obj = parser.readObject(); + parser.close(); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); + KeyPair kp; + if (obj == null) { + throw new NullPointerException("Unable to decode PEM key:" + pem); + } else if (obj instanceof PEMEncryptedKeyPair) { + // Encrypted key - we will use provided password + PEMEncryptedKeyPair ckp = (PEMEncryptedKeyPair) obj; + PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder() + .build(passphrase.toCharArray()); + kp = converter.getKeyPair(ckp.decryptKeyPair(decProv)); + } else if (obj instanceof PrivateKeyInfo) { + PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) obj; + return converter.getPrivateKey(privateKeyInfo); + } else { + // Unencrypted key - no password needed + PEMKeyPair ukp = (PEMKeyPair) obj; + kp = converter.getKeyPair(ukp); + } + return kp.getPrivate(); + } + + public static PublicKey readPublicKey(String pem, String passphrase) throws IOException { + + PEMParser parser = new PEMParser(new CharArrayReader(pem.toCharArray())); + Object obj = parser.readObject(); + parser.close(); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); + KeyPair kp; + if (obj == null) { + throw new NullPointerException("Unable to decode PEM key:" + pem); + } else if (obj instanceof PEMEncryptedKeyPair) { + // Encrypted key - we will use provided password + PEMEncryptedKeyPair ckp = (PEMEncryptedKeyPair) obj; + PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder() + .build(passphrase.toCharArray()); + kp = converter.getKeyPair(ckp.decryptKeyPair(decProv)); + } else if (obj instanceof SubjectPublicKeyInfo) { + SubjectPublicKeyInfo privateKeyInfo = (SubjectPublicKeyInfo) obj; + return converter.getPublicKey(privateKeyInfo); + } else { + // Unencrypted key - no password needed + PEMKeyPair ukp = (PEMKeyPair) obj; + kp = converter.getKeyPair(ukp); + } + return kp.getPublic(); + } + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/util/package-info.java b/eiam-common/src/main/java/cn/topiam/employee/common/util/package-info.java new file mode 100644 index 00000000..fc58057c --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/util/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.common.util; \ No newline at end of file diff --git a/eiam-common/src/main/resources/config/logback-spring.xml b/eiam-common/src/main/resources/config/logback-spring.xml new file mode 100644 index 00000000..2e31559a --- /dev/null +++ b/eiam-common/src/main/resources/config/logback-spring.xml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + utf-8 + + + + + + ${CONSOLE_LOG_PATTERN} + utf-8 + + + ERROR + ACCEPT + DENY + + + ${ROOT}%d/%d-error-%i.log + ${MAXHISTORY} + + ${FILESIZE} + + + + + + + ${CONSOLE_LOG_PATTERN} + utf-8 + + + WARN + ACCEPT + DENY + + + ${ROOT}%d/%d-warn-%i.log + ${MAXHISTORY} + + ${FILESIZE} + + + + + + + ${CONSOLE_LOG_PATTERN} + utf-8 + + + INFO + ACCEPT + DENY + + + ${ROOT}%d/%d-info-%i.log + ${MAXHISTORY} + + ${FILESIZE} + + + + + + + ${CONSOLE_LOG_PATTERN} + utf-8 + + + DEBUG + ACCEPT + DENY + + + ${ROOT}%d/%d-debug-%i.log + ${MAXHISTORY} + + ${FILESIZE} + + + + + + + ${CONSOLE_LOG_PATTERN} + utf-8 + + + TRACE + ACCEPT + DENY + + + ${ROOT}%d/%d-trace-%i.log + ${MAXHISTORY} + + ${FILESIZE} + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eiam-common/src/main/resources/db/changelog/administrator-changelog.xml b/eiam-common/src/main/resources/db/changelog/administrator-changelog.xml new file mode 100644 index 00000000..417a4260 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/administrator-changelog.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/app-changelog.xml b/eiam-common/src/main/resources/db/changelog/app-changelog.xml new file mode 100644 index 00000000..3acdc71f --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/app-changelog.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/app_access_policy-changelog.xml b/eiam-common/src/main/resources/db/changelog/app_access_policy-changelog.xml new file mode 100644 index 00000000..7a9375d4 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/app_access_policy-changelog.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/app_account-changelog.xml b/eiam-common/src/main/resources/db/changelog/app_account-changelog.xml new file mode 100644 index 00000000..a1b17ec5 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/app_account-changelog.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/app_cert-changelog.xml b/eiam-common/src/main/resources/db/changelog/app_cert-changelog.xml new file mode 100644 index 00000000..a6e6d2ce --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/app_cert-changelog.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/app_oidc_config-changelog.xml b/eiam-common/src/main/resources/db/changelog/app_oidc_config-changelog.xml new file mode 100644 index 00000000..7613ce3a --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/app_oidc_config-changelog.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/app_permission_action-changelog.xml b/eiam-common/src/main/resources/db/changelog/app_permission_action-changelog.xml new file mode 100644 index 00000000..65d290d9 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/app_permission_action-changelog.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/app_permission_policy-changelog.xml b/eiam-common/src/main/resources/db/changelog/app_permission_policy-changelog.xml new file mode 100644 index 00000000..356bfae7 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/app_permission_policy-changelog.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/app_permission_resource-changelog.xml b/eiam-common/src/main/resources/db/changelog/app_permission_resource-changelog.xml new file mode 100644 index 00000000..db230196 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/app_permission_resource-changelog.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/app_permission_role-changelog.xml b/eiam-common/src/main/resources/db/changelog/app_permission_role-changelog.xml new file mode 100644 index 00000000..f9bccb39 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/app_permission_role-changelog.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/app_saml2_config-changelog.xml b/eiam-common/src/main/resources/db/changelog/app_saml2_config-changelog.xml new file mode 100644 index 00000000..9f361e74 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/app_saml2_config-changelog.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/audit-changelog.xml b/eiam-common/src/main/resources/db/changelog/audit-changelog.xml new file mode 100644 index 00000000..ca2b3a1c --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/audit-changelog.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/identity_provider-changelog.xml b/eiam-common/src/main/resources/db/changelog/identity_provider-changelog.xml new file mode 100644 index 00000000..2026fade --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/identity_provider-changelog.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eiam-common/src/main/resources/db/changelog/identity_source-changelog.xml b/eiam-common/src/main/resources/db/changelog/identity_source-changelog.xml new file mode 100644 index 00000000..a9076f45 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/identity_source-changelog.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eiam-common/src/main/resources/db/changelog/identity_source_event_record-changelog.xml b/eiam-common/src/main/resources/db/changelog/identity_source_event_record-changelog.xml new file mode 100644 index 00000000..6b371e49 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/identity_source_event_record-changelog.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eiam-common/src/main/resources/db/changelog/identity_source_sync_history-changelog.xml b/eiam-common/src/main/resources/db/changelog/identity_source_sync_history-changelog.xml new file mode 100644 index 00000000..cbcd7b13 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/identity_source_sync_history-changelog.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eiam-common/src/main/resources/db/changelog/identity_source_sync_record-changelog.xml b/eiam-common/src/main/resources/db/changelog/identity_source_sync_record-changelog.xml new file mode 100644 index 00000000..8127440a --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/identity_source_sync_record-changelog.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eiam-common/src/main/resources/db/changelog/mail_send_record-changelog.xml b/eiam-common/src/main/resources/db/changelog/mail_send_record-changelog.xml new file mode 100644 index 00000000..081dc3b6 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/mail_send_record-changelog.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/mail_template-changelog.xml b/eiam-common/src/main/resources/db/changelog/mail_template-changelog.xml new file mode 100644 index 00000000..f8055832 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/mail_template-changelog.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/organization-changelog.xml b/eiam-common/src/main/resources/db/changelog/organization-changelog.xml new file mode 100644 index 00000000..2c18e666 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/organization-changelog.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/organization_member-changelog.xml b/eiam-common/src/main/resources/db/changelog/organization_member-changelog.xml new file mode 100644 index 00000000..8de7b968 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/organization_member-changelog.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/setting-changlog.xml b/eiam-common/src/main/resources/db/changelog/setting-changlog.xml new file mode 100644 index 00000000..100c107d --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/setting-changlog.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/sms_send_record-changelog.xml b/eiam-common/src/main/resources/db/changelog/sms_send_record-changelog.xml new file mode 100644 index 00000000..91e50b60 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/sms_send_record-changelog.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/user-changelog.xml b/eiam-common/src/main/resources/db/changelog/user-changelog.xml new file mode 100644 index 00000000..f581695e --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/user-changelog.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/user_detail-changelog.xml b/eiam-common/src/main/resources/db/changelog/user_detail-changelog.xml new file mode 100644 index 00000000..6e9c0ba9 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/user_detail-changelog.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/user_group-changelog.xml b/eiam-common/src/main/resources/db/changelog/user_group-changelog.xml new file mode 100644 index 00000000..5a9d79b3 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/user_group-changelog.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/user_group_member-changelog.xml b/eiam-common/src/main/resources/db/changelog/user_group_member-changelog.xml new file mode 100644 index 00000000..dac8f0f0 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/user_group_member-changelog.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/changelog/user_history_password-changelog.xml b/eiam-common/src/main/resources/db/changelog/user_history_password-changelog.xml new file mode 100644 index 00000000..0f478818 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/user_history_password-changelog.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eiam-common/src/main/resources/db/changelog/user_idp_bind-changlog.xml b/eiam-common/src/main/resources/db/changelog/user_idp_bind-changlog.xml new file mode 100644 index 00000000..bc179af1 --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/user_idp_bind-changlog.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-common/src/main/resources/db/eiam-changelog-master.xml b/eiam-common/src/main/resources/db/eiam-changelog-master.xml new file mode 100644 index 00000000..86ea095d --- /dev/null +++ b/eiam-common/src/main/resources/db/eiam-changelog-master.xml @@ -0,0 +1,29 @@ + + + + + + diff --git a/eiam-common/src/main/resources/dictionaries/10k-most-common.txt b/eiam-common/src/main/resources/dictionaries/10k-most-common.txt new file mode 100644 index 00000000..c03abe88 --- /dev/null +++ b/eiam-common/src/main/resources/dictionaries/10k-most-common.txt @@ -0,0 +1,10000 @@ +password +123456 +12345678 +1234 +qwerty +12345 +dragon +pussy +baseball +football +letmein +monkey +696969 +abc123 +mustang +michael +shadow +master +jennifer +111111 +2000 +jordan +superman +harley +1234567 +fuckme +hunter +fuckyou +trustno1 +ranger +buster +thomas +tigger +robert +soccer +fuck +batman +test +pass +killer +hockey +george +charlie +andrew +michelle +love +sunshine +jessica +asshole +6969 +pepper +daniel +access +123456789 +654321 +joshua +maggie +starwars +silver +william +dallas +yankees +123123 +ashley +666666 +hello +amanda +orange +biteme +freedom +computer +sexy +thunder +nicole +ginger +heather +hammer +summer +corvette +taylor +fucker +austin +1111 +merlin +matthew +121212 +golfer +cheese +princess +martin +chelsea +patrick +richard +diamond +yellow +bigdog +secret +asdfgh +sparky +cowboy +camaro +anthony +matrix +falcon +iloveyou +bailey +guitar +jackson +purple +scooter +phoenix +aaaaaa +morgan +tigers +porsche +mickey +maverick +cookie +nascar +peanut +justin +131313 +money +horny +samantha +panties +steelers +joseph +snoopy +boomer +whatever +iceman +smokey +gateway +dakota +cowboys +eagles +chicken +dick +black +zxcvbn +please +andrea +ferrari +knight +hardcore +melissa +compaq +coffee +booboo +bitch +johnny +bulldog +xxxxxx +welcome +james +player +ncc1701 +wizard +scooby +charles +junior +internet +bigdick +mike +brandy +tennis +blowjob +banana +monster +spider +lakers +miller +rabbit +enter +mercedes +brandon +steven +fender +john +yamaha +diablo +chris +boston +tiger +marine +chicago +rangers +gandalf +winter +bigtits +barney +edward +raiders +porn +badboy +blowme +spanky +bigdaddy +johnson +chester +london +midnight +blue +fishing +000000 +hannah +slayer +11111111 +rachel +sexsex +redsox +thx1138 +asdf +marlboro +panther +zxcvbnm +arsenal +oliver +qazwsx +mother +victoria +7777777 +jasper +angel +david +winner +crystal +golden +butthead +viking +jack +iwantu +shannon +murphy +angels +prince +cameron +girls +madison +wilson +carlos +hooters +willie +startrek +captain +maddog +jasmine +butter +booger +angela +golf +lauren +rocket +tiffany +theman +dennis +liverpoo +flower +forever +green +jackie +muffin +turtle +sophie +danielle +redskins +toyota +jason +sierra +winston +debbie +giants +packers +newyork +jeremy +casper +bubba +112233 +sandra +lovers +mountain +united +cooper +driver +tucker +helpme +fucking +pookie +lucky +maxwell +8675309 +bear +suckit +gators +5150 +222222 +shithead +fuckoff +jaguar +monica +fred +happy +hotdog +tits +gemini +lover +xxxxxxxx +777777 +canada +nathan +victor +florida +88888888 +nicholas +rosebud +metallic +doctor +trouble +success +stupid +tomcat +warrior +peaches +apples +fish +qwertyui +magic +buddy +dolphins +rainbow +gunner +987654 +freddy +alexis +braves +cock +2112 +1212 +cocacola +xavier +dolphin +testing +bond007 +member +calvin +voodoo +7777 +samson +alex +apollo +fire +tester +walter +beavis +voyager +peter +porno +bonnie +rush2112 +beer +apple +scorpio +jonathan +skippy +sydney +scott +red123 +power +gordon +travis +beaver +star +jackass +flyers +boobs +232323 +zzzzzz +steve +rebecca +scorpion +doggie +legend +ou812 +yankee +blazer +bill +runner +birdie +bitches +555555 +parker +topgun +asdfasdf +heaven +viper +animal +2222 +bigboy +4444 +arthur +baby +private +godzilla +donald +williams +lifehack +phantom +dave +rock +august +sammy +cool +brian +platinum +jake +bronco +paul +mark +frank +heka6w2 +copper +billy +cumshot +garfield +willow +cunt +little +carter +slut +albert +69696969 +kitten +super +jordan23 +eagle1 +shelby +america +11111 +jessie +house +free +123321 +chevy +bullshit +white +broncos +horney +surfer +nissan +999999 +saturn +airborne +elephant +marvin +shit +action +adidas +qwert +kevin +1313 +explorer +walker +police +christin +december +benjamin +wolf +sweet +therock +king +online +dickhead +brooklyn +teresa +cricket +sharon +dexter +racing +penis +gregory +0000 +teens +redwings +dreams +michigan +hentai +magnum +87654321 +nothing +donkey +trinity +digital +333333 +stella +cartman +guinness +123abc +speedy +buffalo +kitty +pimpin +eagle +einstein +kelly +nelson +nirvana +vampire +xxxx +playboy +louise +pumpkin +snowball +test123 +girl +sucker +mexico +beatles +fantasy +ford +gibson +celtic +marcus +cherry +cassie +888888 +natasha +sniper +chance +genesis +hotrod +reddog +alexande +college +jester +passw0rd +bigcock +smith +lasvegas +carmen +slipknot +3333 +death +kimberly +1q2w3e +eclipse +1q2w3e4r +stanley +samuel +drummer +homer +montana +music +aaaa +spencer +jimmy +carolina +colorado +creative +hello1 +rocky +goober +friday +bollocks +scotty +abcdef +bubbles +hawaii +fluffy +mine +stephen +horses +thumper +5555 +pussies +darkness +asdfghjk +pamela +boobies +buddha +vanessa +sandman +naughty +douglas +honda +matt +azerty +6666 +shorty +money1 +beach +loveme +4321 +simple +poohbear +444444 +badass +destiny +sarah +denise +vikings +lizard +melanie +assman +sabrina +nintendo +water +good +howard +time +123qwe +november +xxxxx +october +leather +bastard +young +101010 +extreme +hard +password1 +vincent +pussy1 +lacrosse +hotmail +spooky +amateur +alaska +badger +paradise +maryjane +poop +crazy +mozart +video +russell +vagina +spitfire +anderson +norman +eric +cherokee +cougar +barbara +long +420420 +family +horse +enigma +allison +raider +brazil +blonde +jones +55555 +dude +drowssap +jeff +school +marshall +lovely +1qaz2wsx +jeffrey +caroline +franklin +booty +molly +snickers +leslie +nipples +courtney +diesel +rocks +eminem +westside +suzuki +daddy +passion +hummer +ladies +zachary +frankie +elvis +reggie +alpha +suckme +simpson +patricia +147147 +pirate +tommy +semperfi +jupiter +redrum +freeuser +wanker +stinky +ducati +paris +natalie +babygirl +bishop +windows +spirit +pantera +monday +patches +brutus +houston +smooth +penguin +marley +forest +cream +212121 +flash +maximus +nipple +bobby +bradley +vision +pokemon +champion +fireman +indian +softball +picard +system +clinton +cobra +enjoy +lucky1 +claire +claudia +boogie +timothy +marines +security +dirty +admin +wildcats +pimp +dancer +hardon +veronica +fucked +abcd1234 +abcdefg +ironman +wolverin +remember +great +freepass +bigred +squirt +justice +francis +hobbes +kermit +pearljam +mercury +domino +9999 +denver +brooke +rascal +hitman +mistress +simon +tony +bbbbbb +friend +peekaboo +naked +budlight +electric +sluts +stargate +saints +bondage +brittany +bigman +zombie +swimming +duke +qwerty1 +babes +scotland +disney +rooster +brenda +mookie +swordfis +candy +duncan +olivia +hunting +blink182 +alicia +8888 +samsung +bubba1 +whore +virginia +general +passport +aaaaaaaa +erotic +liberty +arizona +jesus +abcd +newport +skipper +rolltide +balls +happy1 +galore +christ +weasel +242424 +wombat +digger +classic +bulldogs +poopoo +accord +popcorn +turkey +jenny +amber +bunny +mouse +007007 +titanic +liverpool +dreamer +everton +friends +chevelle +carrie +gabriel +psycho +nemesis +burton +pontiac +connor +eatme +lickme +roland +cumming +mitchell +ireland +lincoln +arnold +spiderma +patriots +goblue +devils +eugene +empire +asdfg +cardinal +brown +shaggy +froggy +qwer +kawasaki +kodiak +people +phpbb +light +54321 +kramer +chopper +hooker +honey +whynot +lesbian +lisa +baxter +adam +snake +teen +ncc1701d +qqqqqq +airplane +britney +avalon +sandy +sugar +sublime +stewart +wildcat +raven +scarface +elizabet +123654 +trucks +wolfpack +pervert +lawrence +raymond +redhead +american +alyssa +bambam +movie +woody +shaved +snowman +tiger1 +chicks +raptor +1969 +stingray +shooter +france +stars +madmax +kristen +sports +jerry +789456 +garcia +simpsons +lights +ryan +looking +chronic +alison +hahaha +packard +hendrix +perfect +service +spring +srinivas +spike +katie +252525 +oscar +brother +bigmac +suck +single +cannon +georgia +popeye +tattoo +texas +party +bullet +taurus +sailor +wolves +panthers +japan +strike +flowers +pussycat +chris1 +loverboy +berlin +sticky +marina +tarheels +fisher +russia +connie +wolfgang +testtest +mature +bass +catch22 +juice +michael1 +nigger +159753 +women +alpha1 +trooper +hawkeye +head +freaky +dodgers +pakistan +machine +pyramid +vegeta +katana +moose +tinker +coyote +infinity +inside +pepsi +letmein1 +bang +control +hercules +morris +james1 +tickle +outlaw +browns +billybob +pickle +test1 +michele +antonio +sucks +pavilion +changeme +caesar +prelude +tanner +adrian +darkside +bowling +wutang +sunset +robbie +alabama +danger +zeppelin +juan +rusty +pppppp +nick +2001 +ping +darkstar +madonna +qwe123 +bigone +casino +cheryl +charlie1 +mmmmmm +integra +wrangler +apache +tweety +qwerty12 +bobafett +simone +none +business +sterling +trevor +transam +dustin +harvey +england +2323 +seattle +ssssss +rose +harry +openup +pandora +pussys +trucker +wallace +indigo +storm +malibu +weed +review +babydoll +doggy +dilbert +pegasus +joker +catfish +flipper +valerie +herman +fuckit +detroit +kenneth +cheyenne +bruins +stacey +smoke +joey +seven +marino +fetish +xfiles +wonder +stinger +pizza +babe +pretty +stealth +manutd +gracie +gundam +cessna +longhorn +presario +mnbvcxz +wicked +mustang1 +victory +21122112 +shelly +awesome +athena +q1w2e3r4 +help +holiday +knicks +street +redneck +12341234 +casey +gizmo +scully +dragon1 +devildog +triumph +eddie +bluebird +shotgun +peewee +ronnie +angel1 +daisy +special +metallica +madman +country +impala +lennon +roscoe +omega +access14 +enterpri +miranda +search +smitty +blizzard +unicorn +tight +rick +ronald +asdf1234 +harrison +trigger +truck +danny +home +winnie +beauty +thailand +1234567890 +cadillac +castle +tyler +bobcat +buddy1 +sunny +stones +asian +freddie +chuck +butt +loveyou +norton +hellfire +hotsex +indiana +short +panzer +lonewolf +trumpet +colors +blaster +12121212 +fireball +logan +precious +aaron +elaine +jungle +atlanta +gold +corona +curtis +nikki +polaris +timber +theone +baller +chipper +orlando +island +skyline +dragons +dogs +benson +licker +goldie +engineer +kong +pencil +basketba +open +hornet +world +linda +barbie +chan +farmer +valentin +wetpussy +indians +larry +redman +foobar +travel +morpheus +bernie +target +141414 +hotstuff +photos +laura +savage +holly +rocky1 +fuck_inside +dollar +turbo +design +newton +hottie +moon +202020 +blondes +4128 +lestat +avatar +future +goforit +random +abgrtyu +jjjjjj +cancer +q1w2e3 +smiley +goldberg +express +virgin +zipper +wrinkle1 +stone +andy +babylon +dong +powers +consumer +dudley +monkey1 +serenity +samurai +99999999 +bigboobs +skeeter +lindsay +joejoe +master1 +aaaaa +chocolat +christia +birthday +stephani +tang +1234qwer +alfred +ball +98765432 +maria +sexual +maxima +77777777 +sampson +buckeye +highland +kristin +seminole +reaper +bassman +nugget +lucifer +airforce +nasty +watson +warlock +2121 +philip +always +dodge +chrissy +burger +bird +snatch +missy +pink +gang +maddie +holmes +huskers +piglet +photo +joanne +hamilton +dodger +paladin +christy +chubby +buckeyes +hamlet +abcdefgh +bigfoot +sunday +manson +goldfish +garden +deftones +icecream +blondie +spartan +julie +harold +charger +brandi +stormy +sherry +pleasure +juventus +rodney +galaxy +holland +escort +zxcvb +planet +jerome +wesley +blues +song +peace +david1 +ncc1701e +1966 +51505150 +cavalier +gambit +karen +sidney +ripper +oicu812 +jamie +sister +marie +martha +nylons +aardvark +nadine +minnie +whiskey +bing +plastic +anal +babylon5 +chang +savannah +loser +racecar +insane +yankees1 +mememe +hansolo +chiefs +fredfred +freak +frog +salmon +concrete +yvonne +zxcv +shamrock +atlantis +warren +wordpass +julian +mariah +rommel +1010 +harris +predator +sylvia +massive +cats +sammy1 +mister +stud +marathon +rubber +ding +trunks +desire +montreal +justme +faster +kathleen +irish +1999 +bertha +jessica1 +alpine +sammie +diamonds +tristan +00000 +swinger +shan +stallion +pitbull +letmein2 +roberto +ready +april +palmer +ming +shadow1 +audrey +chong +clitoris +wang +shirley +fuckers +jackoff +bluesky +sundance +renegade +hollywoo +151515 +bernard +wolfman +soldier +picture +pierre +ling +goddess +manager +nikita +sweety +titans +hang +fang +ficken +niners +bottom +bubble +hello123 +ibanez +webster +sweetpea +stocking +323232 +tornado +lindsey +content +bruce +buck +aragorn +griffin +chen +campbell +trojan +christop +newman +wayne +tina +rockstar +father +geronimo +pascal +crimson +brooks +hector +penny +anna +google +camera +chandler +fatcat +lovelove +cody +cunts +waters +stimpy +finger +cindy +wheels +viper1 +latin +robin +greenday +987654321 +creampie +brendan +hiphop +willy +snapper +funtime +duck +trombone +adult +cotton +cookies +kaiser +mulder +westham +latino +jeep +ravens +aurora +drizzt +madness +energy +kinky +314159 +sophia +stefan +slick +rocker +55555555 +freeman +french +mongoose +speed +dddddd +hong +henry +hungry +yang +catdog +cheng +ghost +gogogo +randy +tottenha +curious +butterfl +mission +january +singer +sherman +shark +techno +lancer +lalala +autumn +chichi +orion +trixie +clifford +delta +bobbob +bomber +holden +kang +kiss +1968 +spunky +liquid +mary +beagle +granny +network +bond +kkkkkk +millie +1973 +biggie +beetle +teacher +susan +toronto +anakin +genius +dream +cocks +dang +bush +karate +snakes +bangkok +callie +fuckyou2 +pacific +daytona +kelsey +infantry +skywalke +foster +felix +sailing +raistlin +vanhalen +huang +herbert +jacob +blackie +tarzan +strider +sherlock +lang +gong +sang +dietcoke +ultimate +tree +shai +sprite +ting +artist +chai +chao +devil +python +ninja +misty +ytrewq +sweetie +superfly +456789 +tian +jing +jesus1 +freedom1 +dian +drpepper +potter +chou +darren +hobbit +violet +yong +shen +phillip +maurice +gloria +nolimit +mylove +biscuit +yahoo +shasta +sex4me +smoker +smile +pebbles +pics +philly +tong +tintin +lesbians +marlin +cactus +frank1 +tttttt +chun +danni +emerald +showme +pirates +lian +dogg +colleen +xiao +xian +tazman +tanker +patton +toshiba +richie +alberto +gotcha +graham +dillon +rang +emily +keng +jazz +bigguy +yuan +woman +tomtom +marion +greg +chaos +fossil +flight +racerx +tuan +creamy +boss +bobo +musicman +warcraft +window +blade +shuang +sheila +shun +lick +jian +microsoft +rong +allen +feng +getsome +sally +quality +kennedy +morrison +1977 +beng +wwwwww +yoyoyo +zhang +seng +teddy +joanna +andreas +harder +luke +qazxsw +qian +cong +chuan +deng +nang +boeing +keeper +western +isabelle +1963 +subaru +sheng +thuglife +teng +jiong +miao +martina +mang +maniac +pussie +tracey +a1b2c3 +clayton +zhou +zhuang +xing +stonecol +snow +spyder +liang +jiang +memphis +regina +ceng +magic1 +logitech +chuang +dark +million +blow +sesame +shao +poison +titty +terry +kuan +kuai +kyle +mian +guan +hamster +guai +ferret +florence +geng +duan +pang +maiden +quan +velvet +nong +neng +nookie +buttons +bian +bingo +biao +zhong +zeng +xiong +zhun +ying +zong +xuan +zang +0.0.000 +suan +shei +shui +sharks +shang +shua +small +peng +pian +piao +liao +meng +miami +reng +guang +cang +change +ruan +diao +luan +lucas +qing +chui +chuo +cuan +nuan +ning +heng +huan +kansas +muscle +monroe +weng +whitney +1passwor +bluemoon +zhui +zhua +xiang +zheng +zhen +zhei +zhao +zhan +yomama +zhai +zhuo +zuan +tarheel +shou +shuo +tiao +lady +leonard +leng +kuang +jiao +13579 +basket +qiao +qiong +qiang +chuai +nian +niao +niang +huai +22222222 +bianca +zhuan +zhuai +shuan +shuai +stardust +jumper +margaret +archie +66666666 +charlott +forget +qwertz +bones +history +milton +waterloo +2002 +stuff +11223344 +office +oldman +preston +trains +murray +vertigo +246810 +black1 +swallow +smiles +standard +alexandr +parrot +luther +user +nicolas +1976 +surfing +pioneer +pete +masters +apple1 +asdasd +auburn +hannibal +frontier +panama +lucy +buffy +brianna +welcome1 +vette +blue22 +shemale +111222 +baggins +groovy +global +turner +181818 +1979 +blades +spanking +life +byteme +lobster +collins +dawg +hilton +japanese +1970 +1964 +2424 +polo +markus +coco +deedee +mikey +1972 +171717 +1701 +strip +jersey +green1 +capital +sasha +sadie +putter +vader +seven7 +lester +marcel +banshee +grendel +gilbert +dicks +dead +hidden +iloveu +1980 +sound +ledzep +michel +147258 +female +bugger +buffett +bryan +hell +kristina +molson +2020 +wookie +sprint +thanks +jericho +102030 +grace +fuckin +mandy +ranger1 +trebor +deepthroat +bonehead +molly1 +mirage +models +1984 +2468 +stuart +showtime +squirrel +pentium +mario +anime +gator +powder +twister +connect +neptune +bruno +butts +engine +eatshit +mustangs +woody1 +shogun +septembe +pooh +jimbo +roger +annie +bacon +center +russian +sabine +damien +mollie +voyeur +2525 +363636 +leonardo +camel +chair +germany +giant +qqqq +nudist +bone +sleepy +tequila +megan +fighter +garrett +dominic +obiwan +makaveli +vacation +walnut +1974 +ladybug +cantona +ccbill +satan +rusty1 +passwor1 +columbia +napoleon +dusty +kissme +motorola +william1 +1967 +zzzz +skater +smut +play +matthew1 +robinson +valley +coolio +dagger +boner +bull +horndog +jason1 +blake +penguins +rescue +griffey +8j4ye3uz +californ +champs +qwertyuiop +portland +queen +colt45 +boat +xxxxxxx +xanadu +tacoma +mason +carpet +gggggg +safety +palace +italia +stevie +picturs +picasso +thongs +tempest +ricardo +roberts +asd123 +hairy +foxtrot +gary +nimrod +hotboy +343434 +1111111 +asdfghjkl +goose +overlord +blood +wood +stranger +454545 +shaolin +sooners +socrates +spiderman +peanuts +maxine +rogers +13131313 +andrew1 +filthy +donnie +ohyeah +africa +national +kenny +keith +monique +intrepid +jasmin +pickles +assass +fright +potato +darwin +hhhhhh +kingdom +weezer +424242 +pepsi1 +throat +romeo +gerard +looker +puppy +butch +monika +suzanne +sweets +temple +laurie +josh +megadeth +analsex +nymets +ddddddd +bigballs +support +stick +today +down +oakland +oooooo +qweasd +chucky +bridge +carrot +chargers +discover +dookie +condor +night +butler +hoover +horny1 +isabella +sunrise +sinner +jojo +megapass +martini +assfuck +grateful +ffffff +abigail +esther +mushroom +janice +jamaica +wright +sims +space +there +timmy +7654321 +77777 +cccccc +gizmodo +roxanne +ralph +tractor +cristina +dance +mypass +hongkong +helena +1975 +blue123 +pissing +thomas1 +redred +rich +basketball +attack +cash +satan666 +drunk +dixie +dublin +bollox +kingkong +katrina +miles +1971 +22222 +272727 +sexx +penelope +thompson +anything +bbbb +battle +grizzly +passat +porter +tracy +defiant +bowler +knickers +monitor +wisdom +wild +slappy +thor +letsgo +robert1 +feet +rush +brownie +hudson +098765 +playing +playtime +lightnin +melvin +atomic +bart +hawk +goku +glory +llllll +qwaszx +cosmos +bosco +knights +bentley +beast +slapshot +lewis +assword +frosty +gillian +sara +dumbass +mallard +dddd +deanna +elwood +wally +159357 +titleist +angelo +aussie +guest +golfing +doobie +loveit +chloe +elliott +werewolf +vipers +janine +1965 +blabla +surf +sucking +tardis +serena +shelley +thegame +legion +rebels +fernando +fast +gerald +sarah1 +double +onelove +loulou +toto +crash +blackcat +0007 +tacobell +soccer1 +jedi +manuel +method +river +chase +ludwig +poopie +derrick +boob +breast +kittycat +isabel +belly +pikachu +thunder1 +thankyou +jose +celeste +celtics +frances +frogger +scoobydo +sabbath +coltrane +budman +willis +jackal +bigger +zzzzz +silvia +sooner +licking +gopher +geheim +lonestar +primus +pooper +newpass +brasil +heather1 +husker +element +moomoo +beefcake +zzzzzzzz +tammy +shitty +smokin +personal +jjjj +anthony1 +anubis +backup +gorilla +fuckface +painter +lowrider +punkrock +traffic +claude +daniela +dale +delta1 +nancy +boys +easy +kissing +kelley +wendy +theresa +amazon +alan +fatass +dodgeram +dingdong +malcolm +qqqqqqqq +breasts +boots +honda1 +spidey +poker +temp +johnjohn +miguel +147852 +archer +asshole1 +dogdog +tricky +crusader +weather +syracuse +spankme +speaker +meridian +amadeus +back +harley1 +falcons +dorothy +turkey50 +kenwood +keyboard +ilovesex +1978 +blackman +shazam +shalom +lickit +jimbob +richmond +roller +carson +check +fatman +funny +garbage +sandiego +loving +magnus +cooldude +clover +mobile +bell +payton +plumber +texas1 +tool +topper +jenna +mariners +rebel +harmony +caliente +celica +fletcher +german +diana +oxford +osiris +orgasm +punkin +porsche9 +tuesday +close +breeze +bossman +kangaroo +billie +latinas +judith +astros +scruffy +donna +qwertyu +davis +hearts +kathy +jammer +java +springer +rhonda +ricky +1122 +goodtime +chelsea1 +freckles +flyboy +doodle +city +nebraska +bootie +kicker +webmaster +vulcan +iverson +191919 +blueeyes +stoner +321321 +farside +rugby +director +pussy69 +power1 +bobbie +hershey +hermes +monopoly +west +birdman +blessed +blackjac +southern +peterpan +thumbs +lawyer +melinda +fingers +fuckyou1 +rrrrrr +a1b2c3d4 +coke +nicola +bohica +heart +elvis1 +kids +blacky +stories +sentinel +snake1 +phoebe +jesse +richard1 +1234abcd +guardian +candyman +fisting +scarlet +dildo +pancho +mandingo +lucky7 +condom +munchkin +billyboy +summer1 +student +sword +skiing +sergio +site +sony +thong +rootbeer +assassin +cassidy +frederic +fffff +fitness +giovanni +scarlett +durango +postal +achilles +dawn +dylan +kisses +warriors +imagine +plymouth +topdog +asterix +hallo +cameltoe +fuckfuck +bridget +eeeeee +mouth +weird +will +sithlord +sommer +toby +theking +juliet +avenger +backdoor +goodbye +chevrole +faith +lorraine +trance +cosworth +brad +houses +homers +eternity +kingpin +verbatim +incubus +1961 +blond +zaphod +shiloh +spurs +station +jennie +maynard +mighty +aliens +hank +charly +running +dogman +omega1 +printer +aggies +chocolate +deadhead +hope +javier +bitch1 +stone55 +pineappl +thekid +lizzie +rockets +ashton +camels +formula +forrest +rosemary +oracle +rain +pussey +porkchop +abcde +clancy +nellie +mystic +inferno +blackdog +steve1 +pauline +alexander +alice +alfa +grumpy +flames +scream +lonely +puffy +proxy +valhalla +unreal +cynthia +herbie +engage +yyyyyy +010101 +solomon +pistol +melody +celeb +flying +gggg +santiago +scottie +oakley +portugal +a12345 +newbie +mmmm +venus +1qazxsw2 +beverly +zorro +work +writer +stripper +sebastia +spread +phil +tobias +links +members +metal +1221 +andre +565656 +funfun +trojans +again +cyber +hurrican +moneys +1x2zkg8w +zeus +thing +tomato +lion +atlantic +celine +usa123 +trans +account +aaaaaaa +homerun +hyperion +kevin1 +blacks +44444444 +skittles +sean +hastings +fart +gangbang +fubar +sailboat +older +oilers +craig +conrad +church +damian +dean +broken +buster1 +hithere +immortal +sticks +pilot +peters +lexmark +jerkoff +maryland +anders +cheers +possum +columbus +cutter +muppet +beautiful +stolen +swordfish +sport +sonic +peter1 +jethro +rockon +asdfghj +pass123 +paper +pornos +ncc1701a +bootys +buttman +bonjour +escape +1960 +becky +bears +362436 +spartans +tinman +threesom +lemons +maxmax +1414 +bbbbb +camelot +chad +chewie +gogo +fusion +saint +dilligaf +nopass +myself +hustler +hunter1 +whitey +beast1 +yesyes +spank +smudge +pinkfloy +patriot +lespaul +annette +hammers +catalina +finish +formula1 +sausage +scooter1 +orioles +oscar1 +over +colombia +cramps +natural +eating +exotic +iguana +bella +suckers +strong +sheena +start +slave +pearl +topcat +lancelot +angelica +magelan +racer +ramona +crunch +british +button +eileen +steph +456123 +skinny +seeking +rockhard +chief +filter +first +freaks +sakura +pacman +poontang +dalton +newlife +homer1 +klingon +watcher +walleye +tasha +tasty +sinatra +starship +steel +starbuck +poncho +amber1 +gonzo +grover +catherin +carol +candle +firefly +goblin +scotch +diver +usmc +huskies +eleven +kentucky +kitkat +israel +beckham +bicycle +yourmom +studio +tara +33333333 +shane +splash +jimmy1 +reality +12344321 +caitlin +focus +sapphire +mailman +raiders1 +clark +ddddd +hopper +excalibu +more +wilbur +illini +imperial +phillips +lansing +maxx +gothic +golfball +carlton +camille +facial +front242 +macdaddy +qwer1234 +vectra +cowboys1 +crazy1 +dannyboy +jane +betty +benny +bennett +leader +martinez +aquarius +barkley +hayden +caught +franky +ffff +floyd +sassy +pppp +pppppppp +prodigy +clarence +noodle +eatpussy +vortex +wanking +beatrice +billy1 +siemens +pedro +phillies +research +groups +carolyn +chevy1 +cccc +fritz +gggggggg +doughboy +dracula +nurses +loco +madrid +lollipop +trout +utopia +chrono +cooler +conner +nevada +wibble +werner +summit +marco +marilyn +1225 +babies +capone +fugazi +panda +mama +qazwsxed +puppies +triton +9876 +command +nnnnnn +ernest +momoney +iforgot +wolfie +studly +shawn +renee +alien +hamburg +81fukkc +741852 +catman +china +forgot +gagging +scott1 +drew +oregon +qweqwe +train +crazybab +daniel1 +cutlass +brothers +holes +heidi +mothers +music1 +what +walrus +1957 +bigtime +bike +xtreme +simba +ssss +rookie +angie +bathing +fresh +sanchez +rotten +maestro +luis +look +turbo1 +99999 +butthole +hhhh +elijah +monty +bender +yoda +shania +shock +phish +thecat +rightnow +reagan +baddog +asia +greatone +gateway1 +randall +abstr +napster +brian1 +bogart +high +hitler +emma +kill +weaver +wildfire +jackson1 +isaiah +1981 +belinda +beaner +yoyo +0.0.0.000 +super1 +select +snuggles +slutty +some +phoenix1 +technics +toon +raven1 +rayray +123789 +1066 +albion +greens +fashion +gesperrt +santana +paint +powell +credit +darling +mystery +bowser +bottle +brucelee +hehehe +kelly1 +mojo +1998 +bikini +woofwoof +yyyy +strap +sites +spears +theodore +julius +richards +amelia +central +f**k +nyjets +punisher +username +vanilla +twisted +bryant +brent +bunghole +here +elizabeth +erica +kimber +viagra +veritas +pony +pool +titts +labtec +lifetime +jenny1 +masterbate +mayhem +redbull +govols +gremlin +505050 +gmoney +rupert +rovers +diamond1 +lorenzo +trident +abnormal +davidson +deskjet +cuddles +nice +bristol +karina +milano +vh5150 +jarhead +1982 +bigbird +bizkit +sixers +slider +star69 +starfish +penetration +tommy1 +john316 +meghan +michaela +market +grant +caligula +carl +flicks +films +madden +railroad +cosmo +cthulhu +bradford +br0d3r +military +bearbear +swedish +spawn +patrick1 +polly +these +todd +reds +anarchy +groove +franco +fuckher +oooo +tyrone +vegas +airbus +cobra1 +christine +clips +delete +duster +kitty1 +mouse1 +monkeys +jazzman +1919 +262626 +swinging +stroke +stocks +sting +pippen +labrador +jordan1 +justdoit +meatball +females +saturday +park +vector +cooter +defender +desert +demon +nike +bubbas +bonkers +english +kahuna +wildman +4121 +sirius +static +piercing +terror +teenage +leelee +marissa +microsof +mechanic +robotech +rated +hailey +chaser +sanders +salsero +nuts +macross +quantum +rachael +tsunami +universe +daddy1 +cruise +nguyen +newpass6 +nudes +hellyeah +vernon +1959 +zaq12wsx +striker +sixty +steele +spice +spectrum +smegma +thumb +jjjjjjjj +mellow +astrid +cancun +cartoon +sabres +samiam +pants +oranges +oklahoma +lust +coleman +denali +nude +noodles +buzz +brest +hooter +mmmmmmmm +warthog +bloody +blueblue +zappa +wolverine +sniffing +lance +jean +jjjjj +harper +calico +freee +rover +door +pooter +closeup +bonsai +evelyn +emily1 +kathryn +keystone +iiii +1955 +yzerman +theboss +tolkien +jill +megaman +rasta +bbbbbbbb +bean +handsome +hal9000 +goofy +gringo +gofish +gizmo1 +samsam +scuba +onlyme +tttttttt +corrado +clown +clapton +deborah +boris +bulls +vivian +jayhawk +bethany +wwww +sharky +seeker +ssssssss +somethin +pillow +thesims +lighter +lkjhgf +melissa1 +marcius2 +barry +guiness +gymnast +casey1 +goalie +godsmack +doug +lolo +rangers1 +poppy +abby +clemson +clipper +deeznuts +nobody +holly1 +elliot +eeee +kingston +miriam +belle +yosemite +sucked +sex123 +sexy69 +pic\'s +tommyboy +lamont +meat +masterbating +marianne +marc +gretzky +happyday +frisco +scratch +orchid +orange1 +manchest +quincy +unbelievable +aberdeen +dawson +nathalie +ne1469 +boxing +hill +korn +intercourse +161616 +1985 +ziggy +supersta +stoney +senior +amature +barber +babyboy +bcfields +goliath +hack +hardrock +children +frodo +scout +scrappy +rosie +qazqaz +tracker +active +craving +commando +cohiba +deep +cyclone +dana +bubba69 +katie1 +mpegs +vsegda +jade +irish1 +better +sexy1 +sinclair +smelly +squerting +lions +jokers +jeanette +julia +jojojo +meathead +ashley1 +groucho +cheetah +champ +firefox +gandalf1 +packer +magnolia +love69 +tyler1 +typhoon +tundra +bobby1 +kenworth +village +volley +beth +wolf359 +0420 +000007 +swimmer +skydive +smokes +patty +peugeot +pompey +legolas +kristy +redhot +rodman +redalert +having +grapes +4runner +carrera +floppy +dollars +ou8122 +quattro +adams +cloud9 +davids +nofear +busty +homemade +mmmmm +whisper +vermont +webmaste +wives +insertion +jayjay +philips +phone +topher +tongue +temptress +midget +ripken +havefun +gretchen +canon +celebrity +five +getting +ghetto +direct +otto +ragnarok +trinidad +usnavy +conover +cruiser +dalshe +nicole1 +buzzard +hottest +kingfish +misfit +moore +milfnew +warlord +wassup +bigsexy +blackhaw +zippy +shearer +tights +thursday +kungfu +labia +journey +meatloaf +marlene +rider +area51 +batman1 +bananas +636363 +cancel +ggggg +paradox +mack +lynn +queens +adults +aikido +cigars +nova +hoosier +eeyore +moose1 +warez +interacial +streaming +313131 +pertinant +pool6123 +mayday +rivers +revenge +animated +banker +baddest +gordon24 +ccccc +fortune +fantasies +touching +aisan +deadman +homepage +ejaculation +whocares +iscool +jamesbon +1956 +1pussy +womam +sweden +skidoo +spock +sssss +petra +pepper1 +pinhead +micron +allsop +amsterda +army +aside +gunnar +666999 +chip +foot +fowler +february +face +fletch +george1 +sapper +science +sasha1 +luckydog +lover1 +magick +popopo +public +ultima +derek +cypress +booker +businessbabe +brandon1 +edwards +experience +vulva +vvvv +jabroni +bigbear +yummy +010203 +searay +secret1 +showing +sinbad +sexxxx +soleil +software +piccolo +thirteen +leopard +legacy +jensen +justine +memorex +marisa +mathew +redwing +rasputin +134679 +anfield +greenbay +gore +catcat +feather +scanner +pa55word +contortionist +danzig +daisy1 +hores +erik +exodus +vinnie +iiiiii +zero +1001 +subway +tank +second +snapple +sneakers +sonyfuck +picks +poodle +test1234 +their +llll +junebug +june +marker +mellon +ronaldo +roadkill +amanda1 +asdfjkl +beaches +greene +great1 +cheerleaers +force +doitnow +ozzy +madeline +radio +tyson +christian +daphne +boxster +brighton +housewifes +emmanuel +emerson +kkkk +mnbvcx +moocow +vides +wagner +janet +1717 +bigmoney +blonds +1000 +storys +stereo +4545 +420247 +seductive +sexygirl +lesbean +live +justin1 +124578 +animals +balance +hansen +cabbage +canadian +gangbanged +dodge1 +dimas +lori +loud +malaka +puss +probes +adriana +coolman +crawford +dante +nacked +hotpussy +erotica +kool +mirror +wearing +implants +intruder +bigass +zenith +woohoo +womans +tanya +tango +stacy +pisces +laguna +krystal +maxell +andyod22 +barcelon +chainsaw +chickens +flash1 +downtown +orgasms +magicman +profit +pusyy +pothead +coconut +chuckie +contact +clevelan +designer +builder +budweise +hotshot +horizon +hole +experienced +mondeo +wifes +1962 +strange +stumpy +smiths +sparks +slacker +piper +pitchers +passwords +laptop +jeremiah +allmine +alliance +bbbbbbb +asscock +halflife +grandma +hayley +88888 +cecilia +chacha +saratoga +sandy1 +santos +doogie +number +positive +qwert40 +transexual +crow +close-up +darrell +bonita +ib6ub9 +volvo +jacob1 +iiiii +beastie +sunnyday +stoned +sonics +starfire +snapon +pictuers +pepe +testing1 +tiberius +lisalisa +lesbain +litle +retard +ripple +austin1 +badgirl +golfgolf +flounder +garage +royals +dragoon +dickie +passwor +ocean +majestic +poppop +trailers +dammit +nokia +bobobo +br549 +emmitt +knock +minime +mikemike +whitesox +1954 +3232 +353535 +seamus +solo +sparkle +sluttey +pictere +titten +lback +1024 +angelina +goodluck +charlton +fingerig +gallaries +goat +ruby +passme +oasis +lockerroom +logan1 +rainman +twins +treasure +absolutely +club +custom +cyclops +nipper +bucket +homepage- +hhhhh +momsuck +indain +2345 +beerbeer +bimmer +susanne +stunner +stevens +456456 +shell +sheba +tootsie +tiny +testerer +reefer +really +1012 +harcore +gollum +545454 +chico +caveman +carole +fordf150 +fishes +gaymen +saleen +doodoo +pa55w0rd +looney +presto +qqqqq +cigar +bogey +brewer +helloo +dutch +kamikaze +monte +wasser +vietnam +visa +japanees +0123 +swords +slapper +peach +jump +marvel +masterbaiting +march +redwood +rolling +1005 +ametuer +chiks +cathy +callaway +fucing +sadie1 +panasoni +mamas +race +rambo +unknown +absolut +deacon +dallas1 +housewife +kristi +keywest +kirsten +kipper +morning +wings +idiot +18436572 +1515 +beating +zxczxc +sullivan +303030 +shaman +sparrow +terrapin +jeffery +masturbation +mick +redfish +1492 +angus +barrett +goirish +hardcock +felicia +forfun +galary +freeporn +duchess +olivier +lotus +pornographic +ramses +purdue +traveler +crave +brando +enter1 +killme +moneyman +welder +windsor +wifey +indon +yyyyy +stretch +taylor1 +4417 +shopping +picher +pickup +thumbnils +johnboy +jets +jess +maureen +anne +ameteur +amateurs +apollo13 +hambone +goldwing +5050 +charley +sally1 +doghouse +padres +pounding +quest +truelove +underdog +trader +crack +climber +bolitas +bravo +hohoho +model +italian +beanie +beretta +wrestlin +stroker +tabitha +sherwood +sexyman +jewels +johannes +mets +marcos +rhino +bdsm +balloons +goodman +grils +happy123 +flamingo +games +route66 +devo +dino +outkast +paintbal +magpie +llllllll +twilight +critter +christie +cupcake +nickel +bullseye +krista +knickerless +mimi +murder +videoes +binladen +xerxes +slim +slinky +pinky +peterson +thanatos +meister +menace +ripley +retired +albatros +balloon +bank +goten +5551212 +getsdown +donuts +divorce +nwo4life +lord +lost +underwear +tttt +comet +deer +damnit +dddddddd +deeznutz +nasty1 +nonono +nina +enterprise +eeeee +misfit99 +milkman +vvvvvv +isaac +1818 +blueboy +beans +bigbutt +wyatt +tech +solution +poetry +toolman +laurel +juggalo +jetski +meredith +barefoot +50spanks +gobears +scandinavian +original +truman +cubbies +nitram +briana +ebony +kings +warner +bilbo +yumyum +zzzzzzz +stylus +321654 +shannon1 +server +secure +silly +squash +starman +steeler +staples +phrases +techniques +laser +135790 +allan +barker +athens +cbr600 +chemical +fester +gangsta +fucku2 +freeze +game +salvador +droopy +objects +passwd +lllll +loaded +louis +manchester +losers +vedder +clit +chunky +darkman +damage +buckshot +buddah +boobed +henti +hillary +webber +winter1 +ingrid +bigmike +beta +zidane +talon +slave1 +pissoff +person +thegreat +living +lexus +matador +readers +riley +roberta +armani +ashlee +goldstar +5656 +cards +fmale +ferris +fuking +gaston +fucku +ggggggg +sauron +diggler +pacers +looser +pounded +premier +pulled +town +trisha +triangle +cornell +collin +cosmic +deeper +depeche +norway +bright +helmet +kristine +kendall +mustard +misty1 +watch +jagger +bertie +berger +word +3x7pxr +silver1 +smoking +snowboar +sonny +paula +penetrating +photoes +lesbens +lambert +lindros +lillian +roadking +rockford +1357 +143143 +asasas +goodboy +898989 +chicago1 +card +ferrari1 +galeries +godfathe +gawker +gargoyle +gangster +rubble +rrrr +onetime +pussyman +pooppoop +trapper +twenty +abraham +cinder +company +newcastl +boricua +bunny1 +boxer +hotred +hockey1 +hooper +edward1 +evan +kris +misery +moscow +milk +mortgage +bigtit +show +snoopdog +three +lionel +leanne +joshua1 +july +1230 +assholes +cedric +fallen +farley +gene +frisky +sanity +script +divine +dharma +lucky13 +property +tricia +akira +desiree +broadway +butterfly +hunt +hotbox +hootie +heat +howdy +earthlink +karma +kiteboy +motley +westwood +1988 +bert +blackbir +biggles +wrench +working +wrestle +slippery +pheonix +penny1 +pianoman +tomorrow +thedude +jenn +jonjon +jones1 +mattie +memory +micheal +roadrunn +arrow +attitude +azzer +seahawks +diehard +dotcom +lola +tunafish +chivas +cinnamon +clouds +deluxe +northern +nuclear +north +boom +boobie +hurley +krishna +momomo +modles +volume +23232323 +bluedog +wwwwwww +zerocool +yousuck +pluto +limewire +link +joung +marcia +awnyce +gonavy +haha +films+pic+galeries +fabian +francois +girsl +fuckthis +girfriend +rufus +drive +uncencored +a123456 +airport +clay +chrisbln +combat +cygnus +cupoi +never +netscape +brett +hhhhhhhh +eagles1 +elite +knockers +kendra +mommy +1958 +tazmania +shonuf +piano +pharmacy +thedog +lips +jillian +jenkins +midway +arsenal1 +anaconda +australi +gromit +gotohell +787878 +66666 +carmex2 +camber +gator1 +ginger1 +fuzzy +seadoo +dorian +lovesex +rancid +uuuuuu +911911 +nature +bulldog1 +helen +health +heater +higgins +kirk +monalisa +mmmmmmm +whiteout +virtual +ventura +jamie1 +japanes +james007 +2727 +2469 +blam +bitchass +believe +zephyr +stiffy +sweet1 +silent +southpar +spectre +tigger1 +tekken +lenny +lakota +lionking +jjjjjjj +medical +megatron +1369 +hawaiian +gymnastic +golfer1 +gunners +7779311 +515151 +famous +glass +screen +rudy +royal +sanfran +drake +optimus +panther1 +love1 +mail +maggie1 +pudding +venice +aaron1 +delphi +niceass +bounce +busted +house1 +killer1 +miracle +momo +musashi +jammin +2003 +234567 +wp2003wp +submit +silence +sssssss +state +spikes +sleeper +passwort +toledo +kume +media +meme +medusa +mantis +remote +reading +reebok +1017 +artemis +hampton +harry1 +cafc91 +fettish +friendly +oceans +oooooooo +mango +ppppp +trainer +troy +uuuu +909090 +cross +death1 +news +bullfrog +hokies +holyshit +eeeeeee +mitch +jasmine1 +& +& +sergeant +spinner +leon +jockey +records +right +babyblue +hans +gooner +474747 +cheeks +cars +candice +fight +glow +pass1234 +parola +okokok +pablo +magical +major +ramsey +poseidon +989898 +confused +circle +crusher +cubswin +nnnn +hollywood +erin +kotaku +milo +mittens +whatsup +vvvvv +iomega +insertions +bengals +bermuda +biit +yellow1 +012345 +spike1 +south +sowhat +pitures +peacock +pecker +theend +juliette +jimmie +romance +augusta +hayabusa +hawkeyes +castro +florian +geoffrey +dolly +lulu +qaz123 +usarmy +twinkle +cloud +chuckles +cold +hounddog +hover +hothot +europa +ernie +kenshin +kojak +mikey1 +water1 +196969 +because +wraith +zebra +wwwww +33333 +simon1 +spider1 +snuffy +philippe +thunderb +teddy1 +lesley +marino13 +maria1 +redline +renault +aloha +antoine +handyman +cerberus +gamecock +gobucks +freesex +duffman +ooooo +papa +nuggets +magician +longbow +preacher +porno1 +county +chrysler +contains +dalejr +darius +darlene +dell +navy +buffy1 +hedgehog +hoosiers +honey1 +hott +heyhey +europe +dutchess +everest +wareagle +ihateyou +sunflowe +3434 +senators +shag +spoon +sonoma +stalker +poochie +terminal +terefon +laurence +maradona +maryann +marty +roman +1007 +142536 +alibaba +america1 +bartman +astro +goth +century +chicken1 +cheater +four +ghost1 +passpass +oral +r2d2c3po +civic +cicero +myxworld +kkkkk +missouri +wishbone +infiniti +jameson +1a2b3c +1qwerty +wonderboy +skip +shojou +stanford +sparky1 +smeghead +poiuy +titanium +torres +lantern +jelly +jeanne +meier +1213 +bayern +basset +gsxr750 +cattle +charlene +fishing1 +fullmoon +gilles +dima +obelix +popo +prissy +ramrod +unique +absolute +bummer +hotone +dynasty +entry +konyor +missy1 +moses +282828 +yeah +xyz123 +stop +426hemi +404040 +seinfeld +simmons +pingpong +lazarus +matthews +marine1 +manning +recovery +12345a +beamer +babyface +greece +gustav +7007 +charity +camilla +ccccccc +faggot +foxy +frozen +gladiato +duckie +dogfood +paranoid +packers1 +longjohn +radical +tuna +clarinet +claudio +circus +danny1 +novell +nights +bonbon +kashmir +kiki +mortimer +modelsne +moondog +monaco +vladimir +insert +1953 +zxc123 +supreme +3131 +sexxx +selena +softail +poipoi +pong +together +mars +martin1 +rogue +alone +avalanch +audia4 +55bgates +cccccccc +chick +came11 +figaro +geneva +dogboy +dnsadm +dipshit +paradigm +othello +operator +officer +malone +post +rafael +valencia +tripod +choice +chopin +coucou +coach +cocksuck +common +creature +borussia +book +browning +heritage +hiziad +homerj +eight +earth +millions +mullet +whisky +jacques +store +4242 +speedo +starcraf +skylar +spaceman +piggy +pierce +tiger2 +legos +lala +jezebel +judy +joker1 +mazda +barton +baker +727272 +chester1 +fishman +food +rrrrrrrr +sandwich +dundee +lumber +magazine +radar +ppppppp +tranny +aaliyah +admiral +comics +cleo +delight +buttfuck +homeboy +eternal +kilroy +kellie +khan +violin +wingman +walmart +bigblue +blaze +beemer +beowulf +bigfish +yyyyyyy +woodie +yeahbaby +0123456 +tbone +style +syzygy +starter +lemon +linda1 +merlot +mexican +11235813 +anita +banner +bangbang +badman +barfly +grease +carla +charles1 +ffffffff +screw +doberman +diane +dogshit +overkill +counter +coolguy +claymore +demons +demo +nomore +normal +brewster +hhhhhhh +hondas +iamgod +enterme +everett +electron +eastside +kayla +minimoni +mybaby +wildbill +wildcard +ipswich +200000 +bearcat +zigzag +yyyyyyyy +xander +sweetnes +369369 +skyler +skywalker +pigeon +peyton +tipper +lilly +asdf123 +alphabet +asdzxc +babybaby +banane +barnes +guyver +graphics +grand +chinook +florida1 +flexible +fuckinside +otis +ursitesux +tototo +trust +tower +adam12 +christma +corey +chrome +buddie +bombers +bunker +hippie +keegan +misfits +vickie +292929 +woofer +wwwwwwww +stubby +sheep +secrets +sparta +stang +spud +sporty +pinball +jorge +just4fun +johanna +maxxxx +rebecca1 +gunther +fatima +fffffff +freeway +garion +score +rrrrr +sancho +outback +maggot +puddin +trial +adrienne +987456 +colton +clyde +brain +brains +hoops +eleanor +dwayne +kirby +mydick +villa +19691969 +bigcat +becker +shiner +silverad +spanish +templar +lamer +juicy +marsha +mike1 +maximum +rhiannon +real +1223 +10101010 +arrows +andres +alucard +baldwin +baron +avenue +ashleigh +haggis +channel +cheech +safari +ross +dog123 +orion1 +paloma +qwerasdf +presiden +vegitto +trees +969696 +adonis +colonel +cookie1 +newyork1 +brigitte +buddyboy +hellos +heineken +dwight +eraser +kerstin +motion +moritz +millwall +visual +jaybird +1983 +beautifu +bitter +yvette +zodiac +steven1 +sinister +slammer +smashing +slick1 +sponge +teddybea +theater +this +ticklish +lipstick +jonny +massage +mann +reynolds +ring +1211 +amazing +aptiva +applepie +bailey1 +guitar1 +chanel +canyon +gagged +fuckme1 +rough +digital1 +dinosaur +punk +98765 +90210 +clowns +cubs +daniels +deejay +nigga +naruto +boxcar +icehouse +hotties +electra +kent +widget +india +insanity +1986 +2004 +best +bluefish +bingo1 +***** +stratus +strength +sultan +storm1 +44444 +4200 +sentnece +season +sexyboy +sigma +smokie +spam +point +pippo +ticket +temppass +joel +manman +medicine +1022 +anton +almond +bacchus +aztnm +axio +awful +bamboo +hakr +gregor +hahahaha +5678 +casanova +caprice +camero1 +fellow +fountain +dupont +dolphin1 +dianne +paddle +magnet +qwert1 +pyon +porsche1 +tripper +vampires +coming +noway +burrito +bozo +highheel +hughes +hookem +eddie1 +ellie +entropy +kkkkkkkk +kkkkkkk +illinois +jacobs +1945 +1951 +24680 +21212121 +100000 +stonecold +taco +subzero +sharp +sexxxy +skolko +shanna +skyhawk +spurs1 +sputnik +piazza +testpass +letter +lane +kurt +jiggaman +matilda +1224 +harvard +hannah1 +525252 +4ever +carbon +chef +federico +ghosts +gina +scorpio1 +rt6ytere +madison1 +loki +raquel +promise +coolness +christina +coldbeer +citadel +brittney +highway +evil +monarch +morgan1 +washingt +1997 +bella1 +berry +yaya +yolanda +superb +taxman +studman +stephanie +3636 +sherri +sheriff +shepherd +poland +pizzas +tiffany1 +toilet +latina +lassie +larry1 +joseph1 +mephisto +meagan +marian +reptile +rico +razor +1013 +barron +hammer1 +gypsy +grande +carroll +camper +chippy +cat123 +call +chimera +fiesta +glock +glenn +domain +dieter +dragonba +onetwo +nygiants +odessa +password2 +louie +quartz +prowler +prophet +towers +ultra +cocker +corleone +dakota1 +cumm +nnnnnnn +natalia +boxers +hugo +heynow +hollow +iceberg +elvira +kittykat +kate +kitchen +wasabi +vikings1 +impact +beerman +string +sleep +splinter +snoopy1 +pipeline +pocket +legs +maple +mickey1 +manuela +mermaid +micro +meowmeow +redbird +alisha +baura +battery +grass +chevys +chestnut +caravan +carina +charmed +fraser +frogman +diving +dogger +draven +drifter +oatmeal +paris1 +longdong +quant4307s +rachel1 +vegitta +cole +cobras +corsair +dadada +noelle +mylife +nine +bowwow +body +hotrats +eastwood +moonligh +modena +wave +illusion +iiiiiii +jayhawks +birgit +zone +sutton +susana +swingers +shocker +shrimp +sexgod +squall +stefanie +squeeze +soul +patrice +poiu +players +tigers1 +toejam +tickler +line +julie1 +jimbo1 +jefferso +juanita +michael2 +rodeo +robot +1023 +annie1 +bball +guess +happy2 +charter +farm +flasher +falcon1 +fiction +fastball +gadget +scrabble +diaper +dirtbike +dinner +oliver1 +partner +paco +lucille +macman +poopy +popper +postman +ttttttt +ursula +acura +cowboy1 +conan +daewoo +cyrus +customer +nation +nemrac58 +nnnnn +nextel +bolton +bobdylan +hopeless +eureka +extra +kimmie +kcj9wx5n +killbill +musica +volkswag +wage +windmill +wert +vintage +iloveyou1 +itsme +bessie +zippo +311311 +starligh +smokey1 +spot +snappy +soulmate +plasma +thelma +tonight +krusty +just4me +mcdonald +marius +rochelle +rebel1 +1123 +alfredo +aubrey +audi +chantal +fick +goaway +roses +sales +rusty2 +dirt +dogbone +doofus +ooooooo +oblivion +mankind +luck +mahler +lllllll +pumper +puck +pulsar +valkyrie +tupac +compass +concorde +costello +cougars +delaware +niceguy +nocturne +bob123 +boating +bronze +hopkins +herewego +hewlett +houhou +hubert +earnhard +eeeeeeee +keller +mingus +mobydick +venture +verizon +imation +1950 +1948 +1949 +223344 +bigbig +blossom +zack +wowwow +sissy +skinner +spiker +square +snooker +sluggo +player1 +junk +jeannie +jsbach +jumbo +jewel +medic +robins +reddevil +reckless +123456a +1125 +1031 +beacon +astra +gumby +hammond +hassan +757575 +585858 +chillin +fuck1 +sander +lowell +radiohea +upyours +trek +courage +coolcool +classics +choochoo +darryl +nikki1 +nitro +bugs +boytoy +ellen +excite +kirsty +kane +wingnut +wireless +icu812 +1master +beatle +bigblock +blanca +wolfen +summer99 +sugar1 +tartar +sexysexy +senna +sexman +sick +someone +soprano +pippin +platypus +pixies +telephon +land +laura1 +laurent +rimmer +road +report +1020 +12qwaszx +arturo +around +hamish +halifax +fishhead +forum +dododo +doit +outside +paramedi +lonesome +mandy1 +twist +uuuuu +uranus +ttttt +butcher +bruce1 +helper +hopeful +eduard +dusty1 +kathy1 +katherin +moonbeam +muscles +monster1 +monkeybo +morton +windsurf +vvvvvvv +vivid +install +1947 +187187 +1941 +1952 +tatiana +susan1 +31415926 +sinned +sexxy +senator +sebastian +shadows +smoothie +snowflak +playstat +playa +playboy1 +toaster +jerry1 +marie1 +mason1 +merlin1 +roger1 +roadster +112358 +1121 +andrea1 +bacardi +auto +hardware +hardy +789789 +5555555 +captain1 +flores +fergus +sascha +rrrrrrr +dome +onion +nutter +lololo +qqqqqqq +quick +undertak +uuuuuuuu +uuuuuuu +criminal +cobain +cindy1 +coors +dani +descent +nimbus +nomad +nanook +norwich +bomb +bombay +broker +hookup +kiwi +winners +jackpot +1a2b3c4d +1776 +beardog +bighead +blast +bird33 +0987 +stress +shot +spooge +pelican +peepee +perry +pointer +titan +thedoors +jeremy1 +annabell +altima +baba +hallie +hate +hardone +5454 +candace +catwoman +flip +faithful +finance +farmboy +farscape +genesis1 +salomon +destroy +papers +option +page +loser1 +lopez +r2d2 +pumpkins +training +chriss +cumcum +ninjas +ninja1 +hung +erika +eduardo +killers +miller1 +islander +jamesbond +intel +jarvis +19841984 +2626 +bizzare +blue12 +biker +yoyoma +sushi +styles +shitface +series +shanti +spanker +steffi +smart +sphinx +please1 +paulie +pistons +tiburon +limited +maxwell1 +mdogg +rockies +armstron +alexia +arlene +alejandr +arctic +banger +audio +asimov +augustus +grandpa +753951 +4you +chilly +care1839 +chapman +flyfish +fantasia +freefall +santa +sandrine +oreo +ohshit +macbeth +madcat +loveya +mallory +rage +quentin +qwerqwer +project +ramirez +colnago +citizen +chocha +cobalt +crystal1 +dabears +nevets +nineinch +broncos1 +helene +huge +edgar +epsilon +easter +kestrel +moron +virgil +winston1 +warrior1 +iiiiiiii +iloveyou2 +1616 +beat +bettina +woowoo +zander +straight +shower +sloppy +specialk +tinkerbe +jellybea +reader +romero +redsox1 +ride +1215 +1112 +annika +arcadia +answer +baggio +base +guido +555666 +carmel +cayman +cbr900rr +chips +gabriell +gertrude +glennwei +roxy +sausages +disco +pass1 +luna +lovebug +macmac +queenie +puffin +vanguard +trip +trinitro +airwolf +abbott +aaa111 +cocaine +cisco +cottage +dayton +deadly +datsun +bricks +bumper +eldorado +kidrock +wizard1 +whiskers +wind +wildwood +istheman +interest +italy +25802580 +benoit +bigones +woodland +wolfpac +strawber +suicide +3030 +sheba1 +sixpack +peace1 +physics +pearson +tigger2 +toad +megan1 +meow +ringo +roll +amsterdam +717171 +686868 +5424 +catherine +canuck +football1 +footjob +fulham +seagull +orgy +lobo +mancity +truth +trace +vancouve +vauxhall +acidburn +derf +myspace1 +boozer +buttercu +howell +hola +easton +minemine +munch +jared +1dragon +biology +bestbuy +bigpoppa +blackout +blowfish +bmw325 +bigbob +stream +talisman +tazz +sundevil +3333333 +skate +shutup +shanghai +shop +spencer1 +slowhand +polish +pinky1 +tootie +thecrow +leroy +jonathon +jubilee +jingle +martine +matrix1 +manowar +michaels +messiah +mclaren +resident +reilly +redbaron +rollins +romans +return +rivera +andromed +athlon +beach1 +badgers +guitars +harald +harddick +gotribe +6996 +7grout +5wr2i7h8 +635241 +chase1 +carver +charlotte +fallout +fiddle +fredrick +fenris +francesc +fortuna +ferguson +fairlane +felipe +felix1 +forward +gasman +frost +fucks +sahara +sassy1 +dogpound +dogbert +divx1 +manila +loretta +priest +pornporn +quasar +venom +987987 +access1 +clippers +daylight +decker +daman +data +dentist +crusty +nathan1 +nnnnnnnn +bruno1 +bucks +brodie +budapest +kittens +kerouac +mother1 +waldo1 +wedding +whistler +whatwhat +wanderer +idontkno +1942 +1946 +bigdawg +bigpimp +zaqwsx +414141 +3000gt +434343 +shoes +serpent +starr +smurf +pasword +tommie +thisisit +lake +john1 +robotics +redeye +rebelz +1011 +alatam +asses +asians +bama +banzai +harvest +gonzalez +hair +hanson +575757 +5329 +cascade +chinese +fatty +fender1 +flower2 +funky +sambo +drummer1 +dogcat +dottie +oedipus +osama +macleod +prozac +private1 +rampage +punch +presley +concord +cook +cinema +cornwall +cleaner +christopher +ciccio +corinne +clutch +corvet07 +daemon +bruiser +boiler +hjkl +eyes +egghead +expert +ethan +kasper +mordor +wasted +jamess +iverson3 +bluesman +zouzou +090909 +1002 +switch +stone1 +4040 +sisters +sexo +shawna +smith1 +sperma +sneaky +polska +thewho +terminat +krypton +lawson +library +lekker +jules +johnson1 +johann +justus +rockie +romano +aspire +bastards +goodie +cheese1 +fenway +fishon +fishin +fuckoff1 +girls1 +sawyer +dolores +desmond +duane +doomsday +pornking +ramones +rabbits +transit +aaaaa1 +clock +delilah +noel +boyz +bookworm +bongo +bunnies +brady +buceta +highbury +henry1 +heels +eastern +krissy +mischief +mopar +ministry +vienna +weston +wildone +vodka +jayson +bigbooty +beavis1 +betsy +xxxxxx1 +yogibear +000001 +0815 +zulu +420000 +september +sigmar +sprout +stalin +peggy +patch +lkjhgfds +lagnaf +rolex +redfox +referee +123123123 +1231 +angus1 +ariana +ballin +attila +hall +greedy +grunt +747474 +carpedie +cecile +caramel +foxylady +field +gatorade +gidget +futbol +frosch +saiyan +schmidt +drums +donner +doggy1 +drum +doudou +pack +pain +nutmeg +quebec +valdepen +trash +triple +tosser +tuscl +track +comfort +choke +comein +cola +deputy +deadpool +bremen +borders +bronson +break +hotass +hotmail1 +eskimo +eggman +koko +kieran +katrin +kordell1 +komodo +mone +munich +vvvvvvvv +winger +jaeger +ivan +jackson5 +2222222 +bergkamp +bennie +bigben +zanzibar +worm +xxx123 +sunny1 +373737 +services +sheridan +slater +slayer1 +snoop +stacie +peachy +thecure +times +little1 +jennaj +marquis +middle +rasta69 +1114 +aries +havana +gratis +calgary +checkers +flanker +salope +dirty1 +draco +dogface +luv2epus +rainbow6 +qwerty123 +umpire +turnip +vbnm +tucson +troll +aileen +codered +commande +damon +nana +neon +nico +nightwin +neil +boomer1 +bushido +hotmail0 +horace +enternow +kaitlyn +keepout +karen1 +mindy +mnbv +viewsoni +volcom +wizards +wine +1995 +berkeley +bite +zach +woodstoc +tarpon +shinobi +starstar +phat +patience +patrol +toolbox +julien +johnny1 +joebob +marble +riders +reflex +120676 +1235 +angelus +anthrax +atlas +hawks +grandam +harlem +hawaii50 +gorgeous +655321 +cabron +challeng +callisto +firewall +firefire +fischer +flyer +flower1 +factory +federal +gambler +frodo1 +funk +sand +sam123 +scania +dingo +papito +passmast +olive +palermo +ou8123 +lock +ranch +pride +randy1 +twiggy +travis1 +transfer +treetop +addict +admin1 +963852 +aceace +clarissa +cliff +cirrus +clifton +colin +bobdole +bonner +bogus +bonjovi +bootsy +boater +elway7 +edison +kelvin +kenny1 +moonshin +montag +moreno +wayne1 +white1 +jazzy +jakejake +1994 +1991 +2828 +blunt +bluejays +beau +belmont +worthy +systems +sensei +southpark +stan +peeper +pharao +pigpen +tomahawk +teensex +leedsutd +larkin +jermaine +jeepster +jimjim +josephin +melons +marlon +matthias +marriage +robocop +1003 +1027 +antelope +azsxdc +gordo +hazard +granada +8989 +7894 +ceasar +cabernet +cheshire +california +chelle +candy1 +fergie +fanny +fidelio +giorgio +fuckhead +ruth +sanford +diego +dominion +devon +panic +longer +mackie +qawsed +trucking +twelve +chloe1 +coral +daddyo +nostromo +boyboy +booster +bucky +honolulu +esquire +dynamite +motor +mollydog +wilder +windows1 +waffle +wallet +warning +virus +washburn +wealth +vincent1 +jabber +jaguars +javelin +irishman +idefix +bigdog1 +blue42 +blanked +blue32 +biteme1 +bearcats +blaine +yessir +sylveste +team +stephan +sunfire +tbird +stryker +3ip76k2 +sevens +sheldon +pilgrim +tenchi +titman +leeds +lithium +lander +linkin +landon +marijuan +mariner +markie +midnite +reddwarf +1129 +123asd +12312312 +allstar +albany +asdf12 +antonia +aspen +hardball +goldfing +7734 +49ers +carlo +chambers +cable +carnage +callum +carlos1 +fitter +fandango +festival +flame +gofast +gamma +fucmy69 +scrapper +dogwood +django +magneto +loose +premium +addison +9999999 +abc1234 +cromwell +newyear +nichole +bookie +burns +bounty +brown1 +bologna +earl +entrance +elway +killjoy +kerry +keenan +kick +klondike +mini +mouser +mohammed +wayer +impreza +irene +insomnia +24682468 +2580 +24242424 +billbill +bellaco +blessing +blues1 +bedford +blanco +blunts +stinks +teaser +streets +sf49ers +shovel +solitude +spikey +sonia +pimpdadd +timeout +toffee +lefty +johndoe +johndeer +mega +manolo +mentor +margie +ratman +ridge +record +rhodes +robin1 +1124 +1210 +1028 +1226 +another +babylove +barbados +harbor +gramma +646464 +carpente +chaos1 +fishbone +fireblad +glasgow +frogs +scissors +screamer +salem +scuba1 +ducks +driven +doggies +dicky +donovan +obsidian +rams +progress +tottenham +aikman +comanche +corolla +clarke +conway +cumslut +cyborg +dancing +boston1 +bong +houdini +helmut +elvisp +edge +keksa12 +misha +monty1 +monsters +wetter +watford +wiseguy +veronika +visitor +janelle +1989 +1987 +20202020 +biatch +beezer +bigguns +blueball +bitchy +wyoming +yankees2 +wrestler +stupid1 +sealteam +sidekick +simple1 +smackdow +sporting +spiral +smeller +sperm +plato +tophat +test2 +theatre +thick +toomuch +leigh +jello +jewish +junkie +maxim +maxime +meadow +remingto +roofer +124038 +1018 +1269 +1227 +123457 +arkansas +alberta +aramis +andersen +beaker +barcelona +baltimor +googoo +goochi +852456 +4711 +catcher +carman +champ1 +chess +fortress +fishfish +firefigh +geezer +rsalinas +samuel1 +saigon +scooby1 +doors +dick1 +devin +doom +dirk +doris +dontknow +load +magpies +manfred +raleigh +vader1 +universa +tulips +defense +mygirl +burn +bowtie +bowman +holycow +heinrich +honeys +enforcer +katherine +minerva +wheeler +witch +waterboy +jaime +irving +1992 +23skidoo +bimbo +blue11 +birddog +woodman +womble +zildjian +030303 +stinker +stoppedby +sexybabe +speakers +slugger +spotty +smoke1 +polopolo +perfect1 +things +torpedo +tender +thrasher +lakeside +lilith +jimmys +jerk +junior1 +marsh +masamune +rice +root +1214 +april1 +allgood +bambi +grinch +767676 +5252 +cherries +chipmunk +cezer121 +carnival +capecod +finder +flint +fearless +goats +funstuff +gideon +savior +seabee +sandro +schalke +salasana +disney1 +duckman +options +pancake +pantera1 +malice +lookin +love123 +lloyd +qwert123 +puppet +prayers +union +tracer +crap +creation +cwoui +nascar24 +hookers +hollie +hewitt +estrella +erection +ernesto +ericsson +edthom +kaylee +kokoko +kokomo +kimball +morales +mooses +monk +walton +weekend +inter +internal +1michael +1993 +19781978 +25252525 +worker +summers +surgery +shibby +shamus +skibum +sheepdog +sex69 +spliff +slipper +spoons +spanner +snowbird +slow +toriamos +temp123 +tennesse +lakers1 +jomama +julio +mazdarx7 +rosario +recon +riddle +room +revolver +1025 +1101 +barney1 +babycake +baylor +gotham +gravity +hallowee +hancock +616161 +515000 +caca +cannabis +castor +chilli +fdsa +getout +fuck69 +gators1 +sail +sable +rumble +dolemite +dork +dickens +duffer +dodgers1 +painting +onions +logger +lorena +lookout +magic32 +port +poon +prime +twat +coventry +citroen +christmas +civicsi +cocksucker +coochie +compaq1 +nancy1 +buzzer +boulder +butkus +bungle +hogtied +honor +hero +hotgirls +hilary +heidi1 +eggplant +mustang6 +mortal +monkey12 +wapapapa +wendy1 +volleyba +vibrate +vicky +bledsoe +blink +birthday4 +woof +xxxxx1 +talk +stephen1 +suburban +stock +tabatha +sheeba +start1 +soccer10 +something +starcraft +soccer12 +peanut1 +plastics +penthous +peterbil +tools +tetsuo +torino +tennis1 +termite +ladder +last +lemmein +lakewood +jughead +melrose +megane +reginald +redone +request +angela1 +alive +alissa +goodgirl +gonzo1 +golden1 +gotyoass +656565 +626262 +capricor +chains +calvin1 +foolish +fallon +getmoney +godfather +gabber +gilligan +runaway +salami +dummy +dungeon +dudedude +dumb +dope +opus +paragon +oxygen +panhead +pasadena +opendoor +odyssey +magellan +lottie +printing +pressure +prince1 +trustme +christa +court +davies +neville +nono +bread +buffet +hound +kajak +killkill +mona +moto +mildred +winner1 +vixen +whiteboy +versace +winona +voyager1 +instant +indy +jackjack +bigal +beech +biggun +blake1 +blue99 +big1 +woods +synergy +success1 +336699 +sixty9 +shark1 +skin +simba1 +sharpe +sebring +spongebo +spunk +springs +sliver +phialpha +password9 +pizza1 +plane +perkins +pookey +tickling +lexingky +lawman +joe123 +jolly +mike123 +romeo1 +redheads +reserve +apple123 +alanis +ariane +antony +backbone +aviation +band +hand +green123 +haley +carlitos +byebye +cartman1 +camden +chewy +camaross +favorite6 +forumwp +franks +ginscoot +fruity +sabrina1 +devil666 +doughnut +pantie +oldone +paintball +lumina +rainbow1 +prosper +total +true +umbrella +ajax +951753 +achtung +abc12345 +compact +color +corn +complete +christi +closer +corndog +deerhunt +darklord +dank +nimitz +brandy1 +bowl +breanna +holidays +hetfield +holein1 +hillbill +hugetits +east +evolutio +kenobi +whiplash +waldo +wg8e3wjf +wing +istanbul +invis +1996 +benton +bigjohn +bluebell +beef +beater +benji +bluejay +xyzzy +wrestling +storage +superior +suckdick +taichi +stellar +stephane +shaker +skirt +seymour +semper +splurge +squeak +pearls +playball +pitch +phyllis +pooky +piss +tomas +titfuck +joemama +johnny5 +marcello +marjorie +married +maxi +rhubarb +rockwell +ratboy +reload +rooney +redd +1029 +1030 +1220 +anchor +bbking +baritone +gryphon +gone +57chevy +494949 +celeron +fishy +gladiator +fucker1 +roswell +dougie +downer +dicker +diva +domingo +donjuan +nympho +omar +praise +racers +trick +trauma +truck1 +trample +acer +corwin +cricket1 +clemente +climax +denmark +cuervo +notnow +nittany +neutron +native +bosco1 +buffa +breaker +hello2 +hydro +estelle +exchange +explore +kisskiss +kittys +kristian +montecar +modem +mississi +mooney +weiner +washington +20012001 +bigdick1 +bibi +benfica +yahoo1 +striper +tabasco +supra +383838 +456654 +seneca +serious +shuttle +socks +stanton +penguin1 +pathfind +testibil +thethe +listen +lightning +lighting +jeter2 +marma +mark1 +metoo +republic +rollin +redleg +redbone +redskin +rocco +1245 +armand +anthony7 +altoids +andrews +barley +away +asswipe +bauhaus +bbbbbb1 +gohome +harrier +golfpro +goldeney +818181 +6666666 +5000 +5rxypn +cameron1 +calling +checker +calibra +fields +freefree +faith1 +fist +fdm7ed +finally +giraffe +glasses +giggles +fringe +gate +georgie +scamper +rrpass1 +screwyou +duffy +deville +dimples +pacino +ontario +passthie +oberon +quest1 +postov1000 +puppydog +puffer +raining +protect +qwerty7 +trey +tribe +ulysses +tribal +adam25 +a1234567 +compton +collie +cleopatr +contract +davide +norris +namaste +myrtle +buffalo1 +bonovox +buckley +bukkake +burning +burner +bordeaux +burly +hun999 +emilie +elmo +enters +enrique +keisha +mohawk +willard +vgirl +whale +vince +jayden +jarrett +1812 +1943 +222333 +bigjim +bigd +zoom +wordup +ziggy1 +yahooo +workout +young1 +written +xmas +zzzzzz1 +surfer1 +strife +sunlight +tasha1 +skunk +shauna +seth +soft +sprinter +peaches1 +planes +pinetree +plum +pimping +theforce +thedon +toocool +leeann +laddie +list +lkjh +lara +joke +jupiter1 +mckenzie +matty +rene +redrose +1200 +102938 +annmarie +alexa +antares +austin31 +ground +goose1 +737373 +78945612 +789987 +6464 +calimero +caster +casper1 +cement +chevrolet +chessie +caddy +chill +child +canucks +feeling +favorite +fellatio +f00tball +francine +gateway2 +gigi +gamecube +giovanna +rugby1 +scheisse +dshade +dudes +dixie1 +owen +offshore +olympia +lucas1 +macaroni +manga +pringles +puff +tribble +trouble1 +ussy +core +clint +coolhand +colonial +colt +debra +darthvad +dealer +cygnusx1 +natalie1 +newark +husband +hiking +errors +eighteen +elcamino +emmett +emilia +koolaid +knight1 +murphy1 +volcano +idunno +2005 +2233 +block +benito +blueberr +biguns +yamahar1 +zapper +zorro1 +0911 +3006 +sixsix +shopper +siobhan +sextoy +stafford +snowboard +speedway +sounds +pokey +peabody +playboy2 +titi +think +toast +toonarmy +lister +lambda +joecool +jonas +joyce +juniper +mercer +max123 +manny +massimo +mariposa +met2002 +reggae +ricky1 +1236 +1228 +1016 +all4one +arianna +baberuth +asgard +gonzales +484848 +5683 +6669 +catnip +chiquita +charisma +capslock +cashmone +chat +figure +galant +frenchy +gizmodo1 +girlies +gabby +garner +screwy +doubled +divers +dte4uw +done +dragonfl +maker +locks +rachelle +treble +twinkie +trailer +tropical +acid +crescent +cooking +cococo +cory +dabomb +daffy +dandfa +cyrano +nathanie +briggs +boners +helium +horton +hoffman +hellas +espresso +emperor +killa +kikimora +wanda +w4g8at +verona +ilikeit +iforget +1944 +20002000 +birthday1 +beatles1 +blue1 +bigdicks +beethove +blacklab +blazers +benny1 +woodwork +0069 +0101 +taffy +susie +survivor +swim +stokes +4567 +shodan +spoiled +steffen +pissed +pavlov +pinnacle +place +petunia +terrell +thirty +toni +tito +teenie +lemonade +lily +lillie +lalakers +lebowski +lalalala +ladyboy +jeeper +joyjoy +mercury1 +mantle +mannn +rocknrol +riversid +reeves +123aaa +11112222 +121314 +1021 +1004 +1120 +allen1 +ambers +amstel +ambrose +alice1 +alleycat +allegro +ambrosia +alley +australia +hatred +gspot +graves +goodsex +hattrick +harpoon +878787 +8inches +4wwvte +cassandr +charlie123 +case +chavez +fighting +gabriela +gatsby +fudge +gerry +generic +gareth +fuckme2 +samm +sage +seadog +satchmo +scxakv +santafe +dipper +dingle +dizzy +outoutout +madmad +london1 +qbg26i +pussy123 +randolph +vaughn +tzpvaw +vamp +comedy +comp +cowgirl +coldplay +dawgs +delaney +nt5d27 +novifarm +needles +notredam +newness +mykids +bryan1 +bouncer +hihihi +honeybee +iceman1 +herring +horn +hook +hotlips +dynamo +klaus +kittie +kappa +kahlua +muffy +mizzou +mohamed +musical +wannabe +wednesda +whatup +weller +waterfal +willy1 +invest +blanche +bear1 +billabon +youknow +zelda +yyyyyy1 +zachary1 +01234567 +070462 +zurich +superstar +storms +tail +stiletto +strat +427900 +sigmachi +shelter +shells +sexy123 +smile1 +sophie1 +stefano +stayout +somerset +smithers +playmate +pinkfloyd +phish1 +payday +thebear +telefon +laetitia +kswbdu +larson +jetta +jerky +melina +metro +revoluti +retire +respect +1216 +1201 +1204 +1222 +1115 +archange +barry1 +handball +676767 +chandra +chewbacc +flesh +furball +gocubs +fruit +fullback +gman +gentle +dunbar +dewalt +dominiqu +diver1 +dhip6a +olemiss +ollie +mandrake +mangos +pretzel +pusssy +tripleh +valdez +vagabond +clean +comment +crew +clovis +deaths +dandan +csfbr5yy +deadspin +darrel +ninguna +noah +ncc74656 +bootsie +bp2002 +bourbon +brennan +bumble +books +hose +heyyou +houston1 +hemlock +hippo +hornets +hurricane +horseman +hogan +excess +extensa +muffin1 +virginie +werdna +idontknow +info +iron +jack1 +1bitch +151nxjmt +bendover +bmwbmw +bills +zaq123 +wxcvbn +surprise +supernov +tahoe +talbot +simona +shakur +sexyone +seviyi +sonja +smart1 +speed1 +pepito +phantom1 +playoffs +terry1 +terrier +laser1 +lite +lancia +johngalt +jenjen +jolene +midori +message +maserati +matteo +mental +miami1 +riffraff +ronald1 +reason +rhythm +1218 +1026 +123987 +1015 +1103 +armada +architec +austria +gotmilk +hawkins +gray +camila +camp +cambridg +charge +camero +flex +foreplay +getoff +glacier +glotest +froggie +gerbil +rugger +sanity72 +salesman +donna1 +dreaming +deutsch +orchard +oyster +palmtree +ophelia +pajero +m5wkqf +magenta +luckyone +treefrog +vantage +usmarine +tyvugq +uptown +abacab +aaaaaa1 +advance +chuck1 +delmar +darkange +cyclones +nate +navajo +nope +border +bubba123 +building +iawgk2 +hrfzlz +dylan1 +enrico +encore +emilio +eclipse1 +killian +kayleigh +mutant +mizuno +mustang2 +video1 +viewer +weed420 +whales +jaguar1 +insight +1990 +159159 +1love +bliss +bears1 +bigtruck +binder +bigboss +blitz +xqgann +yeahyeah +zeke +zardoz +stickman +table +3825 +signal +sentra +side +shiva +skipper1 +singapor +southpaw +sonora +squid +slamdunk +slimjim +placid +photon +placebo +pearl1 +test12 +therock1 +tiger123 +leinad +legman +jeepers +joeblow +mccarthy +mike23 +redcar +rhinos +rjw7x4 +1102 +13576479 +112211 +alcohol +gwju3g +greywolf +7bgiqk +7878 +535353 +4snz9g +candyass +cccccc1 +carola +catfight +cali +fister +fosters +finland +frankie1 +gizzmo +fuller +royalty +rugrat +sandie +rudolf +dooley +dive +doreen +dodo +drop +oemdlg +out3xf +paddy +opennow +puppy1 +qazwsxedc +pregnant +quinn +ramjet +under +uncle +abraxas +corner +creed +cocoa +crown +cows +cn42qj +dancer1 +death666 +damned +nudity +negative +nimda2k +buick +bobb +braves1 +brook +henrik +higher +hooligan +dust +everlast +karachi +mortis +mulligan +monies +motocros +wally1 +weapon +waterman +view +willie1 +vicki +inspiron +1test +2929 +bigblack +xytfu7 +yackwin +zaq1xsw2 +yy5rbfsc +100100 +0660 +tahiti +takehana +talks +332211 +3535 +sedona +seawolf +skydiver +shine +spleen +slash +spjfet +special1 +spooner +slimshad +sopranos +spock1 +penis1 +patches1 +terri +thierry +thething +toohot +large +limpone +johnnie +mash4077 +matchbox +masterp +maxdog +ribbit +reed +rita +rockin +redhat +rising +1113 +14789632 +1331 +allday +aladin +andrey +amethyst +ariel +anytime +baseball1 +athome +basil +goofy1 +greenman +gustavo +goofball +ha8fyp +goodday +778899 +charon +chappy +castillo +caracas +cardiff +capitals +canada1 +cajun +catter +freddy1 +favorite2 +frazier +forme +follow +forsaken +feelgood +gavin +gfxqx686 +garlic +sarge +saskia +sanjose +russ +salsa +dilbert1 +dukeduke +downhill +longhair +loop +locutus +lockdown +malachi +mamacita +lolipop +rainyday +pumpkin1 +punker +prospect +rambo1 +rainbows +quake +twin +trinity1 +trooper1 +aimee +citation +coolcat +crappy +default +dental +deniro +d9ungl +daddys +napoli +nautica +nermal +bukowski +brick +bubbles1 +bogota +board +branch +breath +buds +hulk +humphrey +hitachi +evans +ender +export +kikiki +kcchiefs +kram +morticia +montrose +mongo +waqw3p +wizzard +visited +whdbtp +whkzyc +image +154ugeiu +1fuck +binky +blind +bigred1 +blubber +benz +becky1 +year2005 +wonderfu +wooden +xrated +0001 +tampabay +survey +tammy1 +stuffer +3mpz4r +3000 +3some +selina +sierra1 +shampoo +silk +shyshy +slapnuts +standby +spartan1 +sprocket +sometime +stanley1 +poker1 +plus +thought +theshit +torture +thinking +lavalamp +light1 +laserjet +jediknig +jjjjj1 +jocelyn +mazda626 +menthol +maximo +margaux +medic1 +release +richter +rhino1 +roach +renate +repair +reveal +1209 +1234321 +amigos +apricot +alexandra +asdfgh1 +hairball +hatter +graduate +grimace +7xm5rq +6789 +cartoons +capcom +cheesy +cashflow +carrots +camping +fanatic +fool +format +fleming +girlie +glover +gilmore +gardner +safeway +ruthie +dogfart +dondon +diapers +outsider +odin +opiate +lollol +love12 +loomis +mallrats +prague +primetime21 +pugsley +program +r29hqq +touch +valleywa +airman +abcdefg1 +darkone +cummer +dempsey +damn +nadia +natedogg +nineball +ndeyl5 +natchez +newone +normandy +nicetits +buddy123 +buddys +homely +husky +iceland +hr3ytm +highlife +holla +earthlin +exeter +eatmenow +kimkim +karine +k2trix +kernel +kirkland +money123 +moonman +miles1 +mufasa +mousey +wilma +wilhelm +whites +warhamme +instinct +jackass1 +2277 +20spanks +blobby +blair +blinky +bikers +blackjack +becca +blue23 +xman +wyvern +085tzzqi +zxzxzx +zsmj2v +suede +t26gn4 +sugars +sylvie +tantra +swoosh +swiss +4226 +4271 +321123 +383pdjvl +shoe +shane1 +shelby1 +spades +spain +smother +soup +sparhawk +pisser +photo1 +pebble +phones +peavey +picnic +pavement +terra +thistle +tokyo +therapy +lives +linden +kronos +lilbit +linux +johnston +material +melanie1 +marbles +redlight +reno +recall +1208 +1138 +1008 +alchemy +aolsucks +alexalex +atticus +auditt +ballet +b929ezzh +goodyear +hanna +griffith +gubber +863abgsg +7474 +797979 +464646 +543210 +4zqauf +4949 +ch5nmk +carlito +chewey +carebear +caleb +checkmat +cheddar +chachi +fever +forgetit +fine +forlife +giants1 +gates +getit +gamble +gerhard +galileo +g3ujwg +ganja +rufus1 +rushmore +scouts +discus +dudeman +olympus +oscars +osprey +madcow +locust +loyola +mammoth +proton +rabbit1 +question +ptfe3xxp +pwxd5x +purple1 +punkass +prophecy +uyxnyd +tyson1 +aircraft +access99 +abcabc +cocktail +colts +civilwar +cleveland +claudia1 +contour +clement +dddddd1 +cypher +denied +dapzu455 +dagmar +daisydog +name +noles +butters +buford +hoochie +hotel +hoser +eddy +ellis +eldiablo +kingrich +mudvayne +motown +mp8o6d +wife +vipergts +italiano +innocent +2055 +2211 +beavers +bloke +blade1 +yamato +zooropa +yqlgr667 +050505 +zxcvbnm1 +zw6syj +suckcock +tango1 +swing +stern +stephens +swampy +susanna +tammie +445566 +333666 +380zliki +sexpot +sexylady +sixtynin +sickboy +spiffy +sleeping +skylark +sparkles +slam +pintail +phreak +places +teller +timtim +tires +thighs +left +latex +llamas +letsdoit +lkjhg +landmark +letters +lizzard +marlins +marauder +metal1 +manu +register +righton +1127 +alain +alcat +amigo +basebal1 +azertyui +attract +azrael +hamper +gotenks +golfgti +gutter +hawkwind +h2slca +harman +grace1 +6chid8 +789654 +canine +casio +cazzo +chamber +cbr900 +cabrio +calypso +capetown +feline +flathead +fisherma +flipmode +fungus +goal +g9zns4 +full +giggle +gabriel1 +fuck123 +saffron +dogmeat +dreamcas +dirtydog +dunlop +douche +dresden +dickdick +destiny1 +pappy +oaktree +lydia +luft4 +puta +prayer +ramada +trumpet1 +vcradq +tulip +tracy71 +tycoon +aaaaaaa1 +conquest +click +chitown +corps +creepers +constant +couples +code +cornhole +danman +dada +density +d9ebk7 +cummins +darth +cute +nash +nirvana1 +nixon +norbert +nestle +brenda1 +bonanza +bundy +buddies +hotspur +heavy +horror +hufmqw +electro +erasure +enough +elisabet +etvww4 +ewyuza +eric1 +kinder +kenken +kismet +klaatu +musician +milamber +willi +waiting +isacs155 +igor +1million +1letmein +x35v8l +yogi +ywvxpz +xngwoj +zippy1 +020202 +**** +stonewal +sweeney +story +sentry +sexsexsex +spence +sonysony +smirnoff +star12 +solace +sledge +states +snyder +star1 +paxton +pentagon +pkxe62 +pilot1 +pommes +paulpaul +plants +tical +tictac +toes +lighthou +lemans +kubrick +letmein22 +letmesee +jys6wz +jonesy +jjjjjj1 +jigga +joelle +mate +merchant +redstorm +riley1 +rosa +relief +14141414 +1126 +allison1 +badboy1 +asthma +auggie +basement +hartley +hartford +hardwood +gumbo +616913 +57np39 +56qhxs +4mnveh +cake +forbes +fatluvr69 +fqkw5m +fidelity +feathers +fresno +godiva +gecko +gladys +gibson1 +gogators +fridge +general1 +saxman +rowing +sammys +scotts +scout1 +sasasa +samoht +dragon69 +ducky +dragonball +driller +p3wqaw +nurse +papillon +oneone +openit +optimist +longshot +portia +rapier +pussy2 +ralphie +tuxedo +ulrike +undertow +trenton +copenhag +come +delldell +culinary +deltas +mytime +nicky +nickie +noname +noles1 +bucker +bopper +bullock +burnout +bryce +hedges +ibilltes +hihje863 +hitter +ekim +espana +eatme69 +elpaso +envelope +express1 +eeeeee1 +eatme1 +karaoke +kara +mustang5 +misses +wellingt +willem +waterski +webcam +jasons +infinite +iloveyou! +jakarta +belair +bigdad +beerme +yoshi +yinyang +zimmer +x24ik3 +063dyjuy +0000007 +ztmfcq +stopit +stooges +survival +stockton +symow8 +strato +2hot4u +ship +simons +skins +shakes +sex1 +shield +snacks +softtail +slimed123 +pizzaman +pipe +pitt +pathetic +pinto +tigercat +tonton +lager +lizzy +juju +john123 +jennings +josiah +jesse1 +jordon +jingles +martian +mario1 +rootedit +rochard +redwine +requiem +riverrat +rats +1117 +1014 +1205 +althea +allie +amor +amiga +alpina +alert +atreides +banana1 +bahamut +hart +golfman +happines +7uftyx +5432 +5353 +5151 +4747 +byron +chatham +chadwick +cherie +foxfire +ffvdj474 +freaked +foreskin +gayboy +gggggg1 +glenda +gameover +glitter +funny1 +scoobydoo +scroll +rudolph +saddle +saxophon +dingbat +digimon +omicron +parsons +ohio +panda1 +loloxx +macintos +lululu +lollypop +racer1 +queen1 +qwertzui +prick +upnfmc +tyrant +trout1 +9skw5g +aceman +adelaide +acls2h +aaabbb +acapulco +aggie +comcast +craft +crissy +cloudy +cq2kph +custer +d6o8pm +cybersex +davecole +darian +crumbs +daisey +davedave +dasani +needle +mzepab +myporn +narnia +nineteen +booger1 +bravo1 +budgie +btnjey +highlander +hotel6 +humbug +edwin +ewtosi +kristin1 +kobe +knuckles +keith1 +katarina +muff +muschi +montana1 +wingchun +wiggle +whatthe +walking +watching +vette1 +vols +virago +intj3a +ishmael +intern +jachin +illmatic +199999 +2010 +beck +blender +bigpenis +bengal +blue1234 +your +zaqxsw +xray +xxxxxxx1 +zebras +yanks +worlds +tadpole +stripes +svetlana +3737 +4343 +3728 +4444444 +368ejhih +solar +sonne +smalls +sniffer +sonata +squirts +pitcher +playstation +pktmxr +pescator +points +texaco +lesbos +lilian +l8v53x +jo9k2jw2 +jimbeam +josie +jimi +jupiter2 +jurassic +marines1 +maya +rocket1 +ringer +14725836 +12345679 +1219 +123098 +1233 +alessand +althor +angelika +arch +armando +alpha123 +basher +barefeet +balboa +bbbbb1 +banks +badabing +harriet +gopack +golfnut +gsxr1000 +gregory1 +766rglqy +8520 +753159 +8dihc6 +69camaro +666777 +cheeba +chino +calendar +cheeky +camel1 +fishcake +falling +flubber +giuseppe +gianni +gloves +gnasher23 +frisbee +fuzzy1 +fuzzball +sauce +save13tx +schatz +russell1 +sandra1 +scrotum +scumbag +sabre +samdog +dripping +dragon12 +dragster +paige +orwell +mainland +lunatic +lonnie +lotion +maine +maddux +qn632o +poophead +rapper +porn4life +producer +rapunzel +tracks +velocity +vanessa1 +ulrich +trueblue +vampire1 +abacus +902100 +crispy +corky +crane +chooch +d6wnro +cutie +deal +dabulls +dehpye +navyseal +njqcw4 +nownow +nigger1 +nightowl +nonenone +nightmar +bustle +buddy2 +boingo +bugman +bulletin +bosshog +bowie +hybrid +hillside +hilltop +hotlegs +honesty +hzze929b +hhhhh1 +hellohel +eloise +evilone +edgewise +e5pftu +eded +embalmer +excalibur +elefant +kenzie +karl +karin +killah +kleenex +mouses +mounta1n +motors +mutley +muffdive +vivitron +winfield +wednesday +w00t88 +iloveit +jarjar +incest +indycar +17171717 +1664 +17011701 +222777 +2663 +beelch +benben +yitbos +yyyyy1 +yasmin +zapata +zzzzz1 +stooge +tangerin +taztaz +stewart1 +summer69 +sweetness +system1 +surveyor +stirling +3qvqod +3way +456321 +sizzle +simhrq +shrink +shawnee +someday +sparty +ssptx452 +sphere +spark +slammed +sober +persian +peppers +ploppy +pn5jvw +poobear +pianos +plaster +testme +tiff +thriller +larissa +lennox +jewell +master12 +messier +rockey +1229 +1217 +1478 +1009 +anastasi +almighty +amonra +aragon +argentin +albino +azazel +grinder +6uldv8 +83y6pv +8888888 +4tlved +515051 +carsten +changes +flanders +flyers88 +ffffff1 +firehawk +foreman +firedog +flashman +ggggg1 +gerber +godspeed +galway +giveitup +funtimes +gohan +giveme +geryfe +frenchie +sayang +rudeboy +savanna +sandals +devine +dougal +drag0n +dga9la +disaster +desktop +only +onlyone +otter +pandas +mafia +lombard +luckys +lovejoy +lovelife +manders +product +qqh92r +qcmfd454 +pork +radar1 +punani +ptbdhw +turtles +undertaker +trs8f7 +tramp +ugejvp +abba +911turbo +acdc +abcd123 +clever +corina +cristian +create +crash1 +colony +crosby +delboy +daniele +davinci +daughter +notebook +niki +nitrox +borabora +bonzai +budd +brisbane +hotter +heeled +heroes +hooyah +hotgirl +i62gbq +horse1 +hills +hpk2qc +epvjb6 +echo +korean +kristie +mnbvc +mohammad +mind +mommy1 +munster +wade +wiccan +wanted +jacket +2369 +bettyboo +blondy +bismark +beanbag +bjhgfi +blackice +yvtte545 +ynot +yess +zlzfrh +wolvie +007bond +****** +tailgate +tanya1 +sxhq65 +stinky1 +3234412 +3ki42x +seville +shimmer +sheryl +sienna +shitshit +skillet +seaman +sooners1 +solaris +smartass +pastor +pasta +pedros +pennywis +pfloyd +tobydog +thetruth +lethal +letme1n +leland +jenifer +mario66 +micky +rocky2 +rewq +ripped +reindeer +1128 +1207 +1104 +1432 +aprilia +allstate +alyson +bagels +basic +baggies +barb +barrage +greatest +gomez +guru +guard +72d5tn +606060 +4wcqjn +caldwell +chance1 +catalog +faust +film +flange +fran +fartman +geil +gbhcf2 +fussball +glen +fuaqz4 +gameboy +garnet +geneviev +rotary +seahawk +russel +saab +seal +samadams +devlt4 +ditto +drevil +drinker +deuce +dipstick +donut +octopus +ottawa +losangel +loverman +porky +q9umoz +rapture +pump +pussy4me +university +triplex +ue8fpw +trent +trophy +turbos +troubles +agent +aaa340 +churchil +crazyman +consult +creepy +craven +class +cutiepie +ddddd1 +dejavu +cuxldv +nettie +nbvibt +nikon +niko +norwood +nascar1 +nolan +bubba2 +boobear +boogers +buff +bullwink +bully +bulldawg +horsemen +escalade +editor +eagle2 +dynamic +ella +efyreg +edition +kidney +minnesot +mogwai +morrow +msnxbi +moonlight +mwq6qlzo +wars +werder +verygood +voodoo1 +wheel +iiiiii1 +159951 +1624 +1911a1 +2244 +bellagio +bedlam +belkin +bill1 +woodrow +xirt2k +worship +?????? +tanaka +swift +susieq +sundown +sukebe +tales +swifty +2fast4u +senate +sexe +sickness +shroom +shaun +seaweed +skeeter1 +status +snicker +sorrow +spanky1 +spook +patti +phaedrus +pilots +pinch +peddler +theo +thumper1 +tessie +tiger7 +tmjxn151 +thematri +l2g7k3 +letmeinn +lazy +jeffjeff +joan +johnmish +mantra +mariana +mike69 +marshal +mart +mazda6 +riptide +robots +rental +1107 +1130 +142857 +11001001 +1134 +armored +alvin +alec +allnight +alright +amatuers +bartok +attorney +astral +baboon +bahamas +balls1 +bassoon +hcleeb +happyman +granite +graywolf +golf1 +gomets +8vjzus +7890 +789123 +8uiazp +5757 +474jdvff +551scasi +50cent +camaro1 +cherry1 +chemist +final +firenze +fishtank +farrell +freewill +glendale +frogfrog +gerhardt +ganesh +same +scirocco +devilman +doodles +dinger +okinawa +olympic +nursing +orpheus +ohmygod +paisley +pallmall +null +lounge +lunchbox +manhatta +mahalo +mandarin +qwqwqw +qguvyt +pxx3eftp +president +rambler +puzzle +poppy1 +turk182 +trotter +vdlxuc +trish +tugboat +valiant +tracie +uwrl7c +chris123 +coaster +cmfnpu +decimal +debbie1 +dandy +daedalus +dede +natasha1 +nissan1 +nancy123 +nevermin +napalm +newcastle +boats +branden +britt +bonghit +hester +ibxnsm +hhhhhh1 +holger +durham +edmonton +erwin +equinox +dvader +kimmy +knulla +mustafa +monsoon +mistral +morgana +monica1 +mojave +month +monterey +mrbill +vkaxcs +victor1 +wacker +wendell +violator +vfdhif +wilson1 +wavpzt +verena +wildstar +winter99 +iqzzt580 +jarrod +imback +1914 +19741974 +1monkey +1q2w3e4r5t +2500 +2255 +blank +bigshow +bigbucks +blackcoc +zoomer +wtcacq +wobble +xmen +xjznq5 +yesterda +yhwnqc +zzzxxx +streak +393939 +2fchbg +skinhead +skilled +shakira +shaft +shadow12 +seaside +sigrid +sinful +silicon +smk7366 +snapshot +sniper1 +soccer11 +staff +slap +smutty +peepers +pleasant +plokij +pdiddy +pimpdaddy +thrust +terran +topaz +today1 +lionhear +littlema +lauren1 +lincoln1 +lgnu9d +laughing +juneau +methos +medina +merlyn +rogue1 +romulus +redshift +1202 +1469 +12locked +arizona1 +alfarome +al9agd +aol123 +altec +apollo1 +arse +baker1 +bbb747 +bach +axeman +astro1 +hawthorn +goodfell +hawks1 +gstring +hannes +8543852 +868686 +4ng62t +554uzpad +5401 +567890 +5232 +catfood +frame +flow +fire1 +flipflop +fffff1 +fozzie +fluff +garrison +fzappa +furious +round +rustydog +sandberg +scarab +satin +ruger +samsung1 +destin +diablo2 +dreamer1 +detectiv +dominick +doqvq3 +drywall +paladin1 +papabear +offroad +panasonic +nyyankee +luetdi +qcfmtz +pyf8ah +puddles +privacy +rainer +pussyeat +ralph1 +princeto +trivia +trewq +tri5a3 +advent +9898 +agyvorc +clarkie +coach1 +courier +contest +christo +corinna +chowder +concept +climbing +cyzkhw +davidb +dad2ownu +days +daredevi +de7mdf +nose +necklace +nazgul +booboo1 +broad +bonzo +brenna +boot +butch1 +huskers1 +hgfdsa +hornyman +elmer +elektra +england1 +elodie +kermit1 +knife +kaboom +minute +modern +motherfucker +morten +mocha +monday1 +morgoth +ward +weewee +weenie +walters +vorlon +website +wahoo +ilovegod +insider +jayman +1911 +1dallas +1900 +1ranger +201jedlz +2501 +1qaz +bertram +bignuts +bigbad +beebee +billows +belize +bebe +wvj5np +wu4etd +yamaha1 +wrinkle5 +zebra1 +yankee1 +zoomzoom +09876543 +0311 +????? +stjabn +tainted +3tmnej +shoot +skooter +skelter +sixteen +starlite +smack +spice1 +stacey1 +smithy +perrin +pollux +peternorth +pixie +paulina +piston +pick +poets +pine +toons +tooth +topspin +kugm7b +legends +jeepjeep +juliana +joystick +junkmail +jojojojo +jonboy +judge +midland +meteor +mccabe +matter +mayfair +meeting +merrill +raul +riches +reznor +rockrock +reboot +reject +robyn +renee1 +roadway +rasta220 +1411 +1478963 +1019 +archery +allman +andyandy +barks +bagpuss +auckland +gooseman +hazmat +gucci +guns +grammy +happydog +greek +7kbe9d +7676 +6bjvpe +5lyedn +5858 +5291 +charlie2 +chas +c7lrwu +candys +chateau +ccccc1 +cardinals +fear +fihdfv +fortune12 +gocats +gaelic +fwsadn +godboy +gldmeo +fx3tuo +fubar1 +garland +generals +gforce +rxmtkp +rulz +sairam +dunhill +division +dogggg +detect +details +doll +drinks +ozlq6qwm +ov3ajy +lockout +makayla +macgyver +mallorca +loves +prima +pvjegu +qhxbij +raphael +prelude1 +totoro +tusymo +trousers +tunnel +valeria +tulane +turtle1 +tracy1 +aerosmit +abbey1 +address +clticic +clueless +cooper1 +comets +collect +corbin +delpiero +derick +cyprus +dante1 +dave1 +nounours +neal +nexus6 +nero +nogard +norfolk +brent1 +booyah +bootleg +buckaroo +bulls23 +bulls1 +booper +heretic +icecube +hellno +hounds +honeydew +hooters1 +hoes +howie +hevnm4 +hugohugo +eighty +epson +evangeli +eeeee1 +eyphed diff --git a/eiam-common/src/main/resources/mail/content/again-verify-mail-content.html b/eiam-common/src/main/resources/mail/content/again-verify-mail-content.html new file mode 100644 index 00000000..63b316e0 --- /dev/null +++ b/eiam-common/src/main/resources/mail/content/again-verify-mail-content.html @@ -0,0 +1,46 @@ + +
+ + + + + + + + + +
+ ${client_name} +
+
+

+ + 亲爱的用户:${user_email} + +

+

您正在进行二次认证,验证码为:${verify_code} ,5分钟内有效,请及时处理。

+

${client_name} 官方团队

+

${time}

+
+
+
\ No newline at end of file diff --git a/eiam-common/src/main/resources/mail/content/bind-mail-content.html b/eiam-common/src/main/resources/mail/content/bind-mail-content.html new file mode 100644 index 00000000..b4771e80 --- /dev/null +++ b/eiam-common/src/main/resources/mail/content/bind-mail-content.html @@ -0,0 +1,47 @@ + +
+ + + + + + + + + +
+ ${client_name} +
+
+

+ + 亲爱的用户:${user_email} + +

+

您正在绑定邮箱, 您的验证码为:${verify_code}, 请尽快验证并保管好验证码。

+

${client_name} 官方团队

+

${time}

+
+
+
+ \ No newline at end of file diff --git a/eiam-common/src/main/resources/mail/content/password-soon-expired-content.html b/eiam-common/src/main/resources/mail/content/password-soon-expired-content.html new file mode 100644 index 00000000..6ea9fe99 --- /dev/null +++ b/eiam-common/src/main/resources/mail/content/password-soon-expired-content.html @@ -0,0 +1,46 @@ + +
+ + + + + + + + + +
+ ${client_name} +
+
+

+ + 亲爱的用户:${user_email} + +

+

您的密码将于${remain_days}天后过期, 请尽快修改密码。

+

${client_name} 官方团队

+

${time}

+
+
+
\ No newline at end of file diff --git a/eiam-common/src/main/resources/mail/content/reset-password-confirm-content.html b/eiam-common/src/main/resources/mail/content/reset-password-confirm-content.html new file mode 100644 index 00000000..2dddb185 --- /dev/null +++ b/eiam-common/src/main/resources/mail/content/reset-password-confirm-content.html @@ -0,0 +1,46 @@ + +
+ + + + + + + + + +
+ ${client_name} +
+
+

+ + 亲爱的用户:${user_email} + +

+

您已成功重置密码,如非本人操作请立即修改邮箱账户密码。

+

${client_name} 官方团队

+

${time}

+
+
+
\ No newline at end of file diff --git a/eiam-common/src/main/resources/mail/content/reset-password-content.html b/eiam-common/src/main/resources/mail/content/reset-password-content.html new file mode 100644 index 00000000..2d2801a3 --- /dev/null +++ b/eiam-common/src/main/resources/mail/content/reset-password-content.html @@ -0,0 +1,46 @@ + +
+ + + + + + + + + +
+ ${client_name} +
+
+

+ + 亲爱的用户:${user_email} + +

+

您正在重置密码, 您的验证码为:${verify_code},请尽快验证并保管好验证码。

+

${client_name} 官方团队

+

${time}

+
+
+
\ No newline at end of file diff --git a/eiam-common/src/main/resources/mail/content/update-bind-mail-content.html b/eiam-common/src/main/resources/mail/content/update-bind-mail-content.html new file mode 100644 index 00000000..ef098abf --- /dev/null +++ b/eiam-common/src/main/resources/mail/content/update-bind-mail-content.html @@ -0,0 +1,47 @@ + +
+ + + + + + + + + +
+ ${client_name} +
+
+

+ + 亲爱的用户:${user_email} + +

+

您正在修改绑定邮箱, 您的验证码为:${verify_code}, 请尽快验证并保管好验证码。

+

${client_name} 官方团队

+

${time}

+
+
+
+ \ No newline at end of file diff --git a/eiam-common/src/main/resources/mail/content/update-password-content.html b/eiam-common/src/main/resources/mail/content/update-password-content.html new file mode 100644 index 00000000..bb132870 --- /dev/null +++ b/eiam-common/src/main/resources/mail/content/update-password-content.html @@ -0,0 +1,46 @@ + +
+ + + + + + + + + +
+ ${client_name} +
+
+

+ + 亲爱的用户:${user_email} + +

+

您正在修改密码, 您的验证码为:${verify_code},请尽快验证并保管好验证码。

+

${client_name} 官方团队

+

${time}

+
+
+
\ No newline at end of file diff --git a/eiam-common/src/main/resources/mail/content/verify-email-content.html b/eiam-common/src/main/resources/mail/content/verify-email-content.html new file mode 100644 index 00000000..b40af66b --- /dev/null +++ b/eiam-common/src/main/resources/mail/content/verify-email-content.html @@ -0,0 +1,54 @@ + +
+ + + + + + + + + +
+ ${client_name} +
+
+

+ + 亲爱的用户:${user_email} + +

+

检测到您的邮箱还未验证, 请点击以下按钮或复制以下链接验证邮箱

+ ${verify_link} +
+
+ 现在验证 +

${client_name} 官方团队

+

${time}

+
+
+
+ \ No newline at end of file diff --git a/eiam-common/src/main/resources/mail/content/warning-content.html b/eiam-common/src/main/resources/mail/content/warning-content.html new file mode 100644 index 00000000..8047298f --- /dev/null +++ b/eiam-common/src/main/resources/mail/content/warning-content.html @@ -0,0 +1,47 @@ + +
+ + + + + + + + + +
+ ${client_name} +
+
+

+ + 亲爱的用户:${user_email} + +

+

您的账号存在风险操作, 验证码为:${verify_code}, 请尽快验证并保管好验证码。

+

${client_name} 官方团队

+

${time}

+
+
+
+ \ No newline at end of file diff --git a/eiam-common/src/main/resources/mail/content/welcome-mail-content.html b/eiam-common/src/main/resources/mail/content/welcome-mail-content.html new file mode 100644 index 00000000..e299447d --- /dev/null +++ b/eiam-common/src/main/resources/mail/content/welcome-mail-content.html @@ -0,0 +1,46 @@ + +
+ + + + + + + + + +
+ ${client_name} +
+
+

亲爱的用户:${user_email}

+

感谢您加入 ${client_name}

+

+ ${client_description} +

+

${client_name} 官方团队

+

${time}

+
+
+
diff --git a/eiam-common/src/main/resources/sms/template/sms-template_en.properties b/eiam-common/src/main/resources/sms/template/sms-template_en.properties new file mode 100644 index 00000000..1f6e4f3b --- /dev/null +++ b/eiam-common/src/main/resources/sms/template/sms-template_en.properties @@ -0,0 +1,30 @@ +# +# eiam-common - Employee Identity and Access Management Program +# Copyright © 2020-2022 TopIAM (support@topiam.cn) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +bind_phone=Verification code: ${verify_code} (valid for ${expire_time} minutes). Do not share this code with anyone. +bind_phone_success=Dear users: Hello! Your account "Username ${username}" has been successfully bound to the current mobile phone number. +update_phone=Verification code: ${verify_code} (valid for ${expire_time} minutes). Do not share this code with anyone. +forget_password=Verification code: ${verify_code} (valid for ${expire_time} minutes). Do not share this code with anyone. +update_password=Verification code: ${verify_code} (valid for ${expire_time} minutes). Do not share this code with anyone. +login=Verification code: ${verify_code} (valid for ${expire_time} minutes). Do not share this code with anyone. +again_verify=Verification code: ${verify_code} (valid for ${expire_time} minutes). Do not share this code with anyone. +welcome_sms=Dear user: ${username}, the initial password of your TopIAM account is ${password}. +reset_password=Verification code: ${verify_code} (valid for ${expire_time} minutes). Do not share this code with anyone. +reset_password_success=Username: ${username}; password: ${password}. Do not share them with anyone. +warning=Dear user: Hello! It is detected that your account "Username ${username}" has an abnormal risk, you can go to User Center-Account Settings-Risk Events to view the details of the abnormality. +password_soon_expired_remind=Your password will expire in ${expire_days} days. Please change your password as soon as possible. \ No newline at end of file diff --git a/eiam-common/src/main/resources/sms/template/sms-template_zh.properties b/eiam-common/src/main/resources/sms/template/sms-template_zh.properties new file mode 100644 index 00000000..19f6be26 --- /dev/null +++ b/eiam-common/src/main/resources/sms/template/sms-template_zh.properties @@ -0,0 +1,30 @@ +# +# eiam-common - Employee Identity and Access Management Program +# Copyright © 2020-2022 TopIAM (support@topiam.cn) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +bind_phone=\u9A8C\u8BC1\u7801${verify_code}\uFF0C\u8BE5\u9A8C\u8BC1\u7801${expire_time}\u5206\u949F\u5185\u6709\u6548\uFF0C\u8BF7\u52FF\u6CC4\u9732\u4E8E\u4ED6\u4EBA\u3002 +bind_phone_success=\u5C0A\u656C\u7684\u7528\u6237\uFF1A\u4F60\u597D\uFF01\u60A8\u7684\u8D26\u53F7\u201C\u7528\u6237\u540D ${username}\u201D\u6210\u529F\u7ED1\u5B9A\u5F53\u524D\u624B\u673A\u53F7\u3002 +update_phone=\u9A8C\u8BC1\u7801${verify_code}\uFF0C\u8BE5\u9A8C\u8BC1\u7801${expire_time}\u5206\u949F\u5185\u6709\u6548\uFF0C\u8BF7\u52FF\u6CC4\u9732\u4E8E\u4ED6\u4EBA\u3002 +forget_password=\u9A8C\u8BC1\u7801${verify_code}\uFF0C\u8BE5\u9A8C\u8BC1\u7801${expire_time}\u5206\u949F\u5185\u6709\u6548\uFF0C\u8BF7\u52FF\u6CC4\u9732\u4E8E\u4ED6\u4EBA\u3002 +update_password=\u9A8C\u8BC1\u7801${verify_code}\uFF0C\u8BE5\u9A8C\u8BC1\u7801${expire_time}\u5206\u949F\u5185\u6709\u6548\uFF0C\u8BF7\u52FF\u6CC4\u9732\u4E8E\u4ED6\u4EBA\u3002 +login=\u9A8C\u8BC1\u7801${verify_code}\uFF0C\u8BE5\u9A8C\u8BC1\u7801${expire_time}\u5206\u949F\u5185\u6709\u6548\uFF0C\u8BF7\u52FF\u6CC4\u9732\u4E8E\u4ED6\u4EBA\u3002 +again_verify=\u9A8C\u8BC1\u7801${verify_code}\uFF0C\u8BE5\u9A8C\u8BC1\u7801${expire_time}\u5206\u949F\u5185\u6709\u6548\uFF0C\u8BF7\u52FF\u6CC4\u9732\u4E8E\u4ED6\u4EBA\u3002 +welcome_sms=\u5c0a\u656c\u7684\u7528\u6237\uff1a${username}\uff0c\u60a8\u7684 TopIAM \u8d26\u6237\u7684\u521d\u59cb\u5bc6\u7801\u662f\uff1a${password} +reset_password=\u9A8C\u8BC1\u7801${verify_code}\uFF0C\u8BE5\u9A8C\u8BC1\u7801${expire_time}\u5206\u949F\u5185\u6709\u6548\uFF0C\u8BF7\u52FF\u6CC4\u9732\u4E8E\u4ED6\u4EBA\u3002 +reset_password_success=\u7528\u6237\u540D${username}\uFF0C\u5BC6\u7801${password}\uFF0C\u8BF7\u52FF\u6CC4\u9732\u4E8E\u4ED6\u4EBA\u3002 +warning=\u5C0A\u656C\u7684\u7528\u6237\uFF1A\u4F60\u597D\uFF01\u68C0\u6D4B\u5230\u60A8\u7684\u8D26\u53F7\u201C\u7528\u6237\u540D ${username}\u201D\u5B58\u5728\u5F02\u5E38\u98CE\u9669\uFF0C\u60A8\u53EF\u81F3\u7528\u6237\u4E2D\u5FC3-\u8D26\u53F7\u8BBE\u7F6E-\u98CE\u9669\u4E8B\u4EF6\u67E5\u770B\u5F02\u5E38\u8BE6\u60C5\u3002 +password_soon_expired_remind=\u60A8\u7684\u5BC6\u7801\u5C06\u4E8E${expire_days}\u5929\u540E\u8FC7\u671F, \u8BF7\u5C3D\u5FEB\u4FEE\u6539\u5BC6\u7801\u3002 \ No newline at end of file diff --git a/eiam-console/Dockerfile b/eiam-console/Dockerfile new file mode 100644 index 00000000..0db3b787 --- /dev/null +++ b/eiam-console/Dockerfile @@ -0,0 +1,48 @@ +# +# eiam-console - Employee Identity and Access Management Program +# Copyright © 2020-2022 TopIAM (support@topiam.cn) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +FROM azul/zulu-openjdk:17-jre as build +WORKDIR /workspace/app + +ARG JAR_FILE=target/topiam-employee-console-*.jar +COPY ${JAR_FILE} target/application.jar +RUN java -Djarmode=layertools -jar target/application.jar extract --destination target/extracted + +FROM azul/zulu-openjdk:17-jre + +ARG EXTRACTED=/workspace/app/target/extracted +WORKDIR topiam +COPY --from=build ${EXTRACTED}/dependencies/ ./ +COPY --from=build ${EXTRACTED}/spring-boot-loader/ ./ +COPY --from=build ${EXTRACTED}/snapshot-dependencies/ ./ +COPY --from=build ${EXTRACTED}/application/ ./ + + +ENV TZ=Asia/Shanghai +RUN sed -i 's/archive.ubuntu.com/cn.archive.ubuntu.com/g' /etc/apt/sources.list \ + && apt-get update \ + && ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \ + && apt-get install tzdata \ + && apt-get clean \ + && apt-get autoclean \ + && apt-get autoremove \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ + +EXPOSE 1898 + +ENTRYPOINT ["java","-XX:TieredStopAtLevel=1","-Djava.security.egd=file:/dev/./urandom","-Dspring.main.lazy-initialization=false","org.springframework.boot.loader.JarLauncher"] \ No newline at end of file diff --git a/eiam-console/pom.xml b/eiam-console/pom.xml new file mode 100644 index 00000000..109448cd --- /dev/null +++ b/eiam-console/pom.xml @@ -0,0 +1,131 @@ + + + + + cn.topiam + eiam + 1.0.0-beta1 + ../pom.xml + + 4.0.0 + + eiam-console + + + + + cn.topiam + eiam-audit + ${project.version} + + + + cn.topiam + eiam-application-all + ${project.version} + + + + cn.topiam + eiam-authentication-all + ${project.version} + + + + cn.topiam + eiam-identity-source-all + ${project.version} + + + + cn.topiam + eiam-protocol-all + ${project.version} + + + + cn.topiam + eiam-core + ${project.version} + + + + + + topiam-employee-console-${project.version} + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin.version} + + + + src/main/console-fe/ + false + + build/** + yarn.lock + src/.umi-production/** + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.source.version} + ${java.target.version} + + + + + + + src/main/console-fe/build + fe + + + src/main/resources + + **/* + + + + + \ No newline at end of file diff --git a/eiam-console/src/main/java/cn/topiam/employee/EiamConsoleApplication.java b/eiam-console/src/main/java/cn/topiam/employee/EiamConsoleApplication.java new file mode 100644 index 00000000..42ed1d85 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/EiamConsoleApplication.java @@ -0,0 +1,36 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.ServletComponentScan; + +/** + * 应用程序启动入口 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/9 + */ +@ServletComponentScan +@SpringBootApplication +public class EiamConsoleApplication { + public static void main(String[] args) { + SpringApplication.run(EiamConsoleApplication.class, args); + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/ServletInitializer.java b/eiam-console/src/main/java/cn/topiam/employee/ServletInitializer.java new file mode 100644 index 00000000..0df482fb --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/ServletInitializer.java @@ -0,0 +1,36 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + * servlet 3.0 + + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/9 + */ +public class ServletInitializer extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(EiamConsoleApplication.class); + } + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/configuration/ConsoleApiConfiguration.java b/eiam-console/src/main/java/cn/topiam/employee/console/configuration/ConsoleApiConfiguration.java new file mode 100644 index 00000000..42a77e55 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/configuration/ConsoleApiConfiguration.java @@ -0,0 +1,140 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.configuration; + +import org.springdoc.core.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +import cn.topiam.employee.EiamConsoleApplication; +import cn.topiam.employee.common.constants.AuthenticationConstants; +import cn.topiam.employee.support.util.AppVersionUtils; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import static cn.topiam.employee.common.constants.AccountConstants.ACCOUNT_API_DOC_GROUP_NAME; +import static cn.topiam.employee.common.constants.AccountConstants.ACCOUNT_API_PATHS; +import static cn.topiam.employee.common.constants.AnalysisConstants.ANALYSIS_GROUP_NAME; +import static cn.topiam.employee.common.constants.AnalysisConstants.ANALYSIS_PATH; +import static cn.topiam.employee.common.constants.AppConstants.APP_GROUP_NAME; +import static cn.topiam.employee.common.constants.AppConstants.APP_PATH; +import static cn.topiam.employee.common.constants.AuthenticationConstants.AUTHENTICATION_PATH; +import static cn.topiam.employee.common.constants.SettingConstants.SETTING_GROUP_NAME; +import static cn.topiam.employee.common.constants.SettingConstants.SETTING_PATH; + +/** + * ApiConfiguration + * + * @author TopIAM + * Created by support@topiam.cn on 2019/5/16 20:28 + */ +@Configuration +public class ConsoleApiConfiguration { + + public ConsoleApiConfiguration(Environment environment) { + this.environment = environment; + } + + /** + * 账户 RestAPI + * + * @return {@link GroupedOpenApi} + */ + @Bean + public GroupedOpenApi accountRestApi() { + return GroupedOpenApi.builder().group(ACCOUNT_API_DOC_GROUP_NAME) + .pathsToMatch(ACCOUNT_API_PATHS).build(); + } + + /** + * 应用管理 RestAPI + * + * @return {@link GroupedOpenApi} + */ + @Bean + public GroupedOpenApi applicationRestApi() { + return GroupedOpenApi.builder().group(APP_GROUP_NAME).pathsToMatch(APP_PATH + "/**") + .build(); + } + + /** + * 系统认证 RestAPI + * + * @return {@link GroupedOpenApi} + */ + @Bean + public GroupedOpenApi authenticationRestApi() { + return GroupedOpenApi.builder().group(AuthenticationConstants.AUTHENTICATION_GROUP_NAME) + .pathsToMatch(AUTHENTICATION_PATH + "/**").build(); + } + + /** + * 分析 RestAPI + * + * @return {@link GroupedOpenApi} + */ + @Bean + public GroupedOpenApi analysisRestApi() { + return GroupedOpenApi.builder().group(ANALYSIS_GROUP_NAME) + .pathsToMatch(ANALYSIS_PATH + "/**").build(); + } + + /** + * 系统设置 RestAPI + * + * @return {@link GroupedOpenApi} + */ + @Bean + public GroupedOpenApi settingRestApi() { + return GroupedOpenApi.builder().group(SETTING_GROUP_NAME).pathsToMatch(SETTING_PATH + "/**") + .build(); + } + + /** + * API INFO + * + * @return {@link Info} + */ + private Info info() { + Contact contact = new Contact(); + contact.setEmail("support@topiam.cn"); + contact.setName("TopIAM"); + contact.setUrl("https://eiam.topiam.cn"); + return new Info() + //title + .title(environment.getProperty("spring.application.name")) + //描述 + .description("REST API 文档") + //服务条款网址 + .termsOfService("https://eiam.topiam.cn") + //内容 + .contact(contact) + //版本 + .version(AppVersionUtils.getVersion(EiamConsoleApplication.class)); + } + + @Bean + public OpenAPI openApi() { + return new OpenAPI().info(info()); + } + + private final Environment environment; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/configuration/ConsoleFrontendConfiguration.java b/eiam-console/src/main/java/cn/topiam/employee/console/configuration/ConsoleFrontendConfiguration.java new file mode 100644 index 00000000..be2f6109 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/configuration/ConsoleFrontendConfiguration.java @@ -0,0 +1,55 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.configuration; + +import java.io.IOException; + +import org.jetbrains.annotations.NotNull; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.resource.PathResourceResolver; + +/** + * 控制台前端配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/12/4 21:49 + */ +@Configuration +public class ConsoleFrontendConfiguration implements WebMvcConfigurer { + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + // 映射静态资源根目录到 frontend + registry.addResourceHandler("/**").addResourceLocations("classpath:/fe/") + .resourceChain(true).addResolver(new PathResourceResolver() { + // 后端匹配不到路由时转给前端 + @Override + protected Resource getResource(@NotNull String resourcePath, + @NotNull Resource location) throws IOException { + Resource resource = super.getResource(resourcePath, location); + if (resource == null) { + resource = super.getResource("index.html", location); + } + return resource; + } + }); + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/configuration/ConsoleSecurityConfiguration.java b/eiam-console/src/main/java/cn/topiam/employee/console/configuration/ConsoleSecurityConfiguration.java new file mode 100644 index 00000000..cc818664 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/configuration/ConsoleSecurityConfiguration.java @@ -0,0 +1,307 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.configuration; + +import java.util.Objects; +import java.util.stream.Collectors; + +import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.*; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; +import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import cn.topiam.employee.common.constants.AuthorizeConstants; +import cn.topiam.employee.common.entity.setting.SettingEntity; +import cn.topiam.employee.common.geo.GeoLocationService; +import cn.topiam.employee.common.repository.setting.SettingRepository; +import cn.topiam.employee.console.security.handler.*; +import cn.topiam.employee.console.security.listener.ConsoleAuthenticationFailureEventListener; +import cn.topiam.employee.console.security.listener.ConsoleAuthenticationSuccessEventListener; +import cn.topiam.employee.console.security.listener.ConsoleLogoutSuccessEventListener; +import cn.topiam.employee.console.security.listener.ConsoleSessionInformationExpiredStrategy; +import cn.topiam.employee.core.endpoint.security.PublicSecretEndpoint; +import cn.topiam.employee.core.security.form.FormLoginSecretFilter; + +import lombok.RequiredArgsConstructor; +import static org.springframework.boot.autoconfigure.security.StaticResourceLocation.*; +import static org.springframework.security.config.Customizer.withDefaults; + +import static cn.topiam.employee.common.constants.AuthorizeConstants.FE_LOGIN; +import static cn.topiam.employee.common.constants.AuthorizeConstants.LOGIN_PATH; +import static cn.topiam.employee.common.constants.ConfigBeanNameConstants.DEFAULT_SECURITY_FILTER_CHAIN; +import static cn.topiam.employee.common.constants.SessionConstants.CURRENT_STATUS; +import static cn.topiam.employee.core.setting.constant.SecuritySettingConstants.*; +import static cn.topiam.employee.support.constant.EiamConstants.*; + +/** + * ConsoleSecurityConfiguration + * + * @author TopIAM + * Created by support@topiam.cn on 2019/9/27 22:54 + */ +@EnableWebSecurity +@RequiredArgsConstructor +public class ConsoleSecurityConfiguration { + /** + * SecurityFilterChain + * + * @param http {@link HttpSecurity} + * @return {@link SecurityFilterChain} + * @throws Exception Exception + */ + @RefreshScope + @Bean(name = DEFAULT_SECURITY_FILTER_CHAIN) + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // @formatter:off + // 系统配置 + http + //认证请求 + .authorizeHttpRequests(authorizeRequests()) + // 表单登录配置 + .formLogin(withFormLoginConfigurerDefaults()) + //x509 + .x509(withDefaults()) + //异常处理 + .exceptionHandling(withExceptionConfigurerDefaults()) + //记住我 + .rememberMe(withRememberMeConfigurerDefaults(settingRepository)) + //CSRF + .csrf(withCsrfConfigurerDefaults()) + //headers + .headers(withHeadersConfigurerDefaults()) + //cors + .cors(withCorsConfigurerDefaults()) + //退出配置 + .logout(withLogoutConfigurerDefaults()) + //会话管理器 + .sessionManagement(withSessionManagementConfigurerDefaults(settingRepository)); + //表单登录解密过滤器 + http.addFilterBefore(new FormLoginSecretFilter(), UsernamePasswordAuthenticationFilter.class); + // @formatter:on + return http.build(); + } + + /** + * 认证请求 + * + * @return {@link AuthorizeHttpRequestsConfigurer} + */ + public Customizer.AuthorizationManagerRequestMatcherRegistry> authorizeRequests() { + //@formatter:off + return registry -> { + //静态资源 + registry.antMatchers( + CSS.getPatterns().collect(Collectors.joining()), + JAVA_SCRIPT.getPatterns().collect(Collectors.joining()), + IMAGES.getPatterns().collect(Collectors.joining()), + WEB_JARS.getPatterns().collect(Collectors.joining()), + FAVICON.getPatterns().collect(Collectors.joining())).permitAll(); + //当前会话状态 + registry.antMatchers(HttpMethod.GET, CURRENT_STATUS).permitAll(); + //公钥 + registry.antMatchers(HttpMethod.GET, PublicSecretEndpoint.PUBLIC_SECRET_PATH).permitAll(); + //发送OPT + registry.antMatchers(HttpMethod.POST, LOGIN_PATH + "/opt/send").permitAll(); + //健康检查端点 + registry.antMatchers(webEndpointProperties.getBasePath()+"/**").permitAll(); + //其他全部认证 + registry.antMatchers(API_PATH+"/**").authenticated(); + }; + } + + /** + * session 管理器 + * + * @return {@link SessionManagementConfigurer} + */ + public Customizer> withSessionManagementConfigurerDefaults(SettingRepository settingRepository) { + SettingEntity setting = settingRepository.findByName(SECURITY_SESSION_MAXIMUM); + return configurer -> { + configurer.sessionFixation().changeSessionId(); + //用户并发 + String defaultSessionMaximum = SECURITY_BASIC_DEFAULT_SETTINGS + .get(SECURITY_SESSION_MAXIMUM); + String sessionMaximum = Objects.isNull(setting) ? defaultSessionMaximum + : "0".equals(setting.getValue()) ? defaultSessionMaximum : setting.getValue(); + configurer.maximumSessions(Integer.parseInt(sessionMaximum)) + .expiredSessionStrategy(new ConsoleSessionInformationExpiredStrategy()); + }; + } + + /** + * session 退出过滤器 + * + * @return {@link LogoutConfigurer} + */ + public Customizer> withLogoutConfigurerDefaults() { + return configurer -> { + configurer.logoutUrl(AuthorizeConstants.LOGOUT); + configurer.logoutSuccessHandler(new ConsoleLogoutSuccessHandler()); + configurer.permitAll(); + }; + } + + /** + * headers 过滤器 + * + * @return {@link HeadersConfigurer} + */ + public Customizer> withHeadersConfigurerDefaults() { + //@formatter:off + return configurer -> { + configurer.xssProtection(xssProtection -> xssProtection.block(false)); + configurer.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin); + configurer.contentSecurityPolicy( + "default-src 'self'; " + + "frame-src 'self' data:; " + + "frame-ancestors 'self' https://eiam.topiam.cn data:; " + + "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://storage.googleapis.com; " + + "style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net 'unsafe-inline'; " + + "img-src 'self' https://img.alicdn.com https://static-legacy.dingtalk.com https://joeschmoe.io data:; " + + "font-src 'self' https://fonts.gstatic.com data:; "+ + "worker-src 'self' https://storage.googleapis.com blob:;"); + configurer.referrerPolicy( + ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN); + configurer + .referrerPolicy( + ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN) + .and().permissionsPolicy().policy( + "camera=(), fullscreen=(self), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), sync-xhr=()"); + configurer.frameOptions().deny(); + }; + //@formatter:on + } + + /** + * Cors 过滤器 + * + * @return {@link HeadersConfigurer} + */ + public Customizer> withCorsConfigurerDefaults() { + return configurer -> configurer.configurationSource(new UrlBasedCorsConfigurationSource()); + } + + /** + * 异常处理器 + * + * @return {@link ExceptionHandlingConfigurer} + */ + public Customizer> withExceptionConfigurerDefaults() { + return configurer -> { + configurer.authenticationEntryPoint(new ConsoleAuthenticationEntryPoint()); + configurer.accessDeniedHandler(new ConsoleAccessDeniedHandler()); + }; + } + + /** + * withRememberMeConfigurerDefaults + * + * @return {@link RememberMeConfigurer} + */ + public Customizer> withRememberMeConfigurerDefaults(SettingRepository settingRepository) { + SpringSessionRememberMeServices rememberMeServices = new SpringSessionRememberMeServices(); + rememberMeServices.setAlwaysRemember(false); + SettingEntity setting = settingRepository.findByName(SECURITY_BASIC_REMEMBER_ME_VALID_TIME); + String rememberMeValiditySeconds = Objects.isNull(setting) + ? SECURITY_BASIC_DEFAULT_SETTINGS.get(SECURITY_BASIC_REMEMBER_ME_VALID_TIME) + : setting.getValue(); + rememberMeServices.setValiditySeconds(Integer.parseInt(rememberMeValiditySeconds)); + return configurer -> configurer.rememberMeServices(rememberMeServices); + } + + /** + * csrf + * + * @return {@link CsrfConfigurer} + */ + public Customizer> withCsrfConfigurerDefaults(RequestMatcher... ignoringRequestMatchers) { + return csrf -> { + CookieCsrfTokenRepository repository = CookieCsrfTokenRepository.withHttpOnlyFalse(); + repository.setCookieName(DEFAULT_CSRF_COOKIE_NAME); + repository.setHeaderName(DEFAULT_CSRF_HEADER_NAME); + csrf.ignoringRequestMatchers(ignoringRequestMatchers).csrfTokenRepository(repository); + }; + } + + /** + * form + * + * @return {@link FormLoginConfigurer} + */ + public Customizer> withFormLoginConfigurerDefaults() { + // @formatter:off + return configurer -> { + configurer.loginPage(FE_LOGIN); + configurer.loginProcessingUrl(AuthorizeConstants.FORM_LOGIN); + configurer.successHandler(new ConsoleAuthenticationSuccessHandler()); + configurer.failureHandler(new ConsoleAuthenticationFailureHandler()); + }; + // @formatter:on + } + + /** + * 身份验证成功事件监听器 + * + * @return {@link ConsoleAuthenticationSuccessEventListener} + */ + @Bean + @ConditionalOnMissingBean + public ConsoleAuthenticationSuccessEventListener authenticationSuccessEventListener() { + return new ConsoleAuthenticationSuccessEventListener(geoLocationService); + } + + /** + * 身份验证失败事件监听器 + * + * @return {@link ConsoleAuthenticationFailureEventListener} + */ + @Bean + @ConditionalOnMissingBean + public ConsoleAuthenticationFailureEventListener authenticationFailureEventListener() { + return new ConsoleAuthenticationFailureEventListener(); + } + + /** + * 退出成功事件监听器 + * + * @return {@link ConsoleLogoutSuccessEventListener} + */ + @Bean + @ConditionalOnMissingBean + public ConsoleLogoutSuccessEventListener logoutSuccessEventListener() { + return new ConsoleLogoutSuccessEventListener(); + } + + private final SettingRepository settingRepository; + + private final GeoLocationService geoLocationService; + + private final WebEndpointProperties webEndpointProperties; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/configuration/package-info.java b/eiam-console/src/main/java/cn/topiam/employee/console/configuration/package-info.java new file mode 100644 index 00000000..1bfb0f01 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/configuration/package-info.java @@ -0,0 +1,24 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +/** + * Configuration + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/10 22:26 + */ +package cn.topiam.employee.console.configuration; diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/constants/package-info.java b/eiam-console/src/main/java/cn/topiam/employee/console/constants/package-info.java new file mode 100644 index 00000000..402e89a8 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/constants/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.constants; \ No newline at end of file diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/CurrentUserEndpoint.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/CurrentUserEndpoint.java new file mode 100644 index 00000000..bf6c9b05 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/CurrentUserEndpoint.java @@ -0,0 +1,131 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller; + +import java.io.Serializable; +import java.util.Optional; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +import com.alibaba.fastjson2.JSON; + +import cn.topiam.employee.common.entity.setting.AdministratorEntity; +import cn.topiam.employee.common.exception.UserNotFoundException; +import cn.topiam.employee.common.repository.setting.AdministratorRepository; +import cn.topiam.employee.core.security.userdetails.UserDetails; +import cn.topiam.employee.core.security.util.SecurityUtils; +import cn.topiam.employee.support.result.ApiRestResult; +import cn.topiam.employee.support.util.DesensitizationUtil; +import cn.topiam.employee.support.util.HttpResponseUtils; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +import io.swagger.v3.oas.annotations.media.Schema; +import static cn.topiam.employee.common.constants.SessionConstants.CURRENT_USER; + +/** + * 当前用户 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/23 20:49 + */ +@Slf4j +@Component +@WebServlet(value = CURRENT_USER) +public class CurrentUserEndpoint extends HttpServlet { + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) { + //当前用户名 + UserDetails userDetails = SecurityUtils.getCurrentUser(); + Optional optional = administratorRepository + .findById(Long.valueOf(userDetails.getId())); + if (optional.isEmpty()) { + SecurityContextHolder.clearContext(); + throw new UserNotFoundException(); + } + AdministratorEntity administrator = optional.get(); + CurrentUserResult result = new CurrentUserResult(); + //用户ID + result.setAccountId(userDetails.getId()); + //用户名 + result.setUsername(administrator.getUsername()); + //头像 + result.setAvatar(administrator.getAvatar()); + //邮箱 + result.setEmail(DesensitizationUtil.emailEncrypt(administrator.getEmail())); + //手机号 + result.setPhone(DesensitizationUtil.phoneEncrypt(administrator.getPhone())); + ApiRestResult build = ApiRestResult. builder() + .result(result).build(); + HttpResponseUtils.flushResponse(resp, JSON.toJSONString(build)); + } + + /** + * 当前用户结果返回 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/10/26 23:16 + */ + @Data + @Schema(description = "当前用户结果") + public static class CurrentUserResult implements Serializable { + /** + * 帐户ID + */ + @Schema(description = "帐户ID") + private String accountId; + + /** + * 用户名 + */ + @Schema(description = "用户名") + private String username; + + /** + * 头像 + */ + @Schema(description = "头像") + private String avatar; + + /** + * 邮箱 + */ + @Schema(description = "邮箱") + private String email; + + /** + * 手机号 + */ + @Schema(description = "手机号") + private String phone; + } + + private final AdministratorRepository administratorRepository; + + public CurrentUserEndpoint(AdministratorRepository administratorRepository) { + this.administratorRepository = administratorRepository; + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/FrontendForwardController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/FrontendForwardController.java new file mode 100644 index 00000000..386e7a62 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/FrontendForwardController.java @@ -0,0 +1,41 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * ForwardController + * + * @author TopIAM + * Created by support@topiam.cn on 2022/12/4 22:16 + */ +@Controller +public class FrontendForwardController { + + /** + * forward + * + * @return forward to client {@code index.html}. + */ + @GetMapping(value = "/") + public String forward() { + return "forward:index.html"; + } +} \ No newline at end of file diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/account/OrganizationController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/account/OrganizationController.java new file mode 100644 index 00000000..35b80a1c --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/account/OrganizationController.java @@ -0,0 +1,241 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.account; + +import java.util.List; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import cn.topiam.employee.audit.annotation.Audit; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.common.constants.AccountConstants; +import cn.topiam.employee.console.pojo.result.account.OrganizationChildResult; +import cn.topiam.employee.console.pojo.result.account.OrganizationResult; +import cn.topiam.employee.console.pojo.result.account.OrganizationRootResult; +import cn.topiam.employee.console.pojo.result.account.OrganizationTreeResult; +import cn.topiam.employee.console.pojo.save.account.OrganizationCreateParam; +import cn.topiam.employee.console.pojo.update.account.OrganizationUpdateParam; +import cn.topiam.employee.console.service.account.OrganizationService; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.result.ApiRestResult; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 系统账户-组织架构 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/11 19:18 + */ +@Validated +@Tag(name = "组织架构") +@RestController +@RequestMapping(value = AccountConstants.ORGANIZATION_PATH, produces = MediaType.APPLICATION_JSON_VALUE) +public class OrganizationController { + + /** + * 创建组织架构 + * + * @param param {@link OrganizationCreateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @PostMapping(value = "/create") + @Audit(type = EventType.CREATE_ORG) + @Operation(summary = "创建组织") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult createOrganization(@RequestBody @Validated OrganizationCreateParam param) { + return ApiRestResult. builder().result(organizationService.createOrg(param)) + .build(); + } + + /** + * 修改组织架构 + * + * @param param {@link OrganizationUpdateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "修改组织") + @Audit(type = EventType.UPDATE_ORG) + @PutMapping(value = "/update") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult updateOrganization(@RequestBody @Validated OrganizationUpdateParam param) { + return ApiRestResult. builder().result(organizationService.updateOrg(param)) + .build(); + } + + /** + * 删除组织架构 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "删除组织") + @Audit(type = EventType.DELETE_ORG) + @DeleteMapping(value = "/delete/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult deleteOrganization(@PathVariable(value = "id") String id) { + return ApiRestResult. builder().result(organizationService.deleteOrg(id)).build(); + } + + /** + * 删除组织架构 + * + * @param ids {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "批量删除组织") + @Audit(type = EventType.DELETE_ORG) + @DeleteMapping(value = "/batch_delete") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult batchDeleteOrganization(String[] ids) { + return ApiRestResult. builder().result(organizationService.batchDeleteOrg(ids)) + .build(); + } + + /** + * 启用组织 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "启用组织") + @PutMapping(value = "/enable/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult enableOrganization(@PathVariable(value = "id") String id) { + return ApiRestResult. builder() + .result(organizationService.updateStatus(id, Boolean.TRUE)).build(); + } + + /** + * 禁用组织 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "禁用组织") + @PutMapping(value = "/disable/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult disableOrganization(@PathVariable(value = "id") String id) { + return ApiRestResult. builder() + .result(organizationService.updateStatus(id, Boolean.FALSE)).build(); + } + + /** + * 移动组织架构 + * + * @param id {@link String} + * @param parentId {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "移动组织") + @Audit(type = EventType.MOVE_ORGANIZATION) + @PutMapping(value = "/move") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult moveOrganization(@RequestParam(value = "id") String id, + @RequestParam(value = "parentId") String parentId) { + return ApiRestResult. builder() + .result(organizationService.moveOrganization(id, parentId)).build(); + } + + /** + * 查询根组织 + * + * @return {@link List} + */ + @GetMapping(value = "/get_root") + @Operation(summary = "获取根组织") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getRootOrganization() { + OrganizationRootResult result = organizationService.getRootOrganization(); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 查询子组织 + * + * @return {@link List} + */ + @GetMapping(value = "/get_child") + @Operation(summary = "获取下级组织") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getChildOrganization(@RequestParam(value = "parentId") String parentId) { + List list = organizationService.getChildOrganization(parentId); + return ApiRestResult.> builder().result(list).build(); + } + + /** + * 查询子组织 + * + * @return {@link List} + */ + @GetMapping(value = "/filter_tree") + @Operation(summary = "获取下级组织") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> filterOrganizationTree(@RequestParam(value = "keyWord") String keyWord) { + List list = organizationService.filterOrganizationTree(keyWord); + return ApiRestResult.> builder().result(list).build(); + } + + /** + * 根据ID查询组织 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Operation(summary = "获取组织信息") + @GetMapping(value = "/get/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getOrganization(@PathVariable(value = "id") String id) { + OrganizationResult result = organizationService.getOrganization(id); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 组织架构服务实现类 + */ + private final OrganizationService organizationService; + + /** + * 构造 + * + * @param organizationService {@link OrganizationService} + */ + public OrganizationController(OrganizationService organizationService) { + this.organizationService = organizationService; + } + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/account/UserController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/account/UserController.java new file mode 100644 index 00000000..7497079b --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/account/UserController.java @@ -0,0 +1,381 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.account; + +import java.io.Serializable; +import java.util.List; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import cn.topiam.employee.audit.annotation.Audit; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.common.entity.account.query.UserListNotInGroupQuery; +import cn.topiam.employee.common.entity.account.query.UserListQuery; +import cn.topiam.employee.common.enums.*; +import cn.topiam.employee.console.pojo.result.account.UserListResult; +import cn.topiam.employee.console.pojo.result.account.UserLoginAuditListResult; +import cn.topiam.employee.console.pojo.result.account.UserResult; +import cn.topiam.employee.console.pojo.result.app.UserIdpBindListResult; +import cn.topiam.employee.console.pojo.save.account.UserCreateParam; +import cn.topiam.employee.console.pojo.update.account.ResetPasswordParam; +import cn.topiam.employee.console.pojo.update.account.UserUpdateParam; +import cn.topiam.employee.console.service.account.UserService; +import cn.topiam.employee.console.service.app.UserIdpBindService; +import cn.topiam.employee.core.security.otp.OtpContextHelp; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.AccountConstants.USER_PATH; + +/** + * 系统账户-用户 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/11 19:18 + */ +@Validated +@Tag(name = "用户管理") +@RestController +@AllArgsConstructor +@RequestMapping(value = USER_PATH, produces = MediaType.APPLICATION_JSON_VALUE) +public class UserController { + + /** + * 获取用户列表 + * + * @param page {@link PageModel} + * @return {@link UserListQuery} + */ + @Operation(summary = "获取用户列表") + @GetMapping(value = "/list") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getUserList(PageModel page, + @Validated UserListQuery query) { + return ApiRestResult.> builder() + .result(userService.getUserList(page, query)).build(); + } + + /** + * 获取用户列表(不在指定用户组) + * + * @param query {@link UserListNotInGroupQuery} 分组ID + * @return {@link Boolean} + */ + @Operation(summary = "获取用户列表(不在指定用户组)") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + @GetMapping(value = "/notin_group_list") + public ApiRestResult> getUserListNotInGroup(PageModel model, + @Validated UserListNotInGroupQuery query) { + return ApiRestResult.> builder() + .result(userService.getUserListNotInGroup(model, query)).build(); + } + + /** + * 创建用户 + * + * @param param {@link UserCreateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "创建用户") + @Audit(type = EventType.CREATE_USER) + @PostMapping(value = "/create") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult createUser(@RequestBody @Validated UserCreateParam param) { + return ApiRestResult. builder().result(userService.createUser(param)).build(); + } + + /** + * 更改系统用户 + * + * @param param {@link UserCreateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "修改用户") + @Audit(type = EventType.UPDATE_USER) + @PutMapping(value = "/update") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult updateUser(@RequestBody @Validated UserUpdateParam param) { + return ApiRestResult. builder().result(userService.updateUser(param)).build(); + } + + /** + * 删除用户 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "删除用户") + @Audit(type = EventType.DELETE_USER) + @DeleteMapping(value = "/delete/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult deleteUser(@PathVariable(value = "id") String id) { + return ApiRestResult. builder().result(userService.deleteUser(id)).build(); + } + + /** + * 批量删除用户 + * + * @param ids {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "批量删除用户") + @Audit(type = EventType.DELETE_USER) + @DeleteMapping(value = "/batch_delete") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult batchDeleteUser(String[] ids) { + return ApiRestResult. builder().result(userService.batchDeleteUser(ids)).build(); + } + + /** + * 获取用户 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Operation(summary = "获取用户信息") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + @GetMapping(value = "/get/{id}") + public ApiRestResult getUser(@PathVariable(value = "id") String id) { + return ApiRestResult. builder().result(userService.getUser(id)).build(); + } + + /** + * 启用用户 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Operation(summary = "启用用户") + @Audit(type = EventType.ENABLE_USER) + @PutMapping(value = "/enable/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult enableUser(@PathVariable(value = "id") String id) { + Boolean result = userService.changeUserStatus(Long.valueOf(id), UserStatus.ENABLE); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 禁用用户 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "禁用用户") + @Audit(type = EventType.DISABLE_USER) + @PutMapping(value = "/disable/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult disableUser(@PathVariable(value = "id") String id) { + Boolean result = userService.changeUserStatus(Long.valueOf(id), UserStatus.DISABLE); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 用户转岗 + * + * @param userId {@link String} + * @param orgId {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "用户转岗") + @PutMapping(value = "/transfer") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult userTransfer(@Parameter(description = "用户ID") @NotBlank(message = "用户ID不能为空") String userId, + @Parameter(description = "组织ID") @NotBlank(message = "组织ID不能为空") String orgId) { + return ApiRestResult. builder().result(userService.userTransfer(userId, orgId)) + .build(); + } + + /** + * 用户离职 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "用户离职") + @Audit(type = EventType.ORG_REMOVE_USER) + @DeleteMapping(value = "/resign/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult userResign(@PathVariable(value = "id") String id) { + return ApiRestResult. builder() + .result(userService.changeUserStatus(Long.valueOf(id), UserStatus.LOCKED)).build(); + } + + /** + * 重置用户密码 + * + * @param param {@link ResetPasswordParam} + * @return Boolean + */ + @Lock + @Preview + @Operation(summary = "重置用户密码") + @Audit(type = EventType.MODIFY_USER_PASSWORD) + @PutMapping(value = "/reset_password") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult resetUserPassword(@Validated @RequestBody ResetPasswordParam param) { + return ApiRestResult. builder().result(userService.resetUserPassword(param)) + .build(); + } + + /** + * 参数有效性验证 + * + * @return {@link Boolean} + */ + @Operation(summary = "参数有效性验证") + @GetMapping(value = "/param_check") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult userParamCheck(@Parameter(description = "验证类型") @NotNull(message = "验证类型不能为空") CheckValidityType type, + @Parameter(description = "值") @NotEmpty(message = "验证值不能为空") String value, + @Parameter(description = "ID") Long id) { + Boolean result = userService.userParamCheck(type, value, id); + //返回 + return ApiRestResult. builder().result(result).build(); + } + + /** + * 查询用户登录审计列表 + * + * @param id {@link Long} + * @param pageModel {@link PageModel} + * @return {@link ApiRestResult} + */ + @Operation(description = "查询用户登录审计列表") + @GetMapping(value = "/login_audit/list") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getUserLoginAuditList(@Parameter(description = "ID") Long id, + PageModel pageModel) { + Page list = userService.findUserLoginAuditList(id, pageModel); + return ApiRestResult.ok(list); + } + + /** + * 查询用户身份提供商绑定 + * + * @param userId {@link String} + * @return {@link UserIdpBindListResult} + */ + @Operation(summary = "查询用户身份提供商绑定") + @GetMapping(value = "/idp_bind") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getUserIdpBindList(@Validated @RequestParam(value = "userId", required = false) @Parameter(description = "ID") @NotBlank(message = "用户ID不能为空") String userId) { + return ApiRestResult.> builder() + .result(userIdpBindService.getUserIdpBindList(userId)).build(); + } + + /** + * 删除用户身份提供商绑定 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "删除用户身份提供商绑定") + @Audit(type = EventType.DELETE_USER_IDP_BIND) + @DeleteMapping(value = "/unbind_idp") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult unbindUserIdp(@Validated @RequestParam(value = "id", required = false) @Parameter(description = "ID") @NotBlank(message = "绑定ID不能为空") String id) { + return ApiRestResult. builder().result(userIdpBindService.unbindUserIdpBind(id)) + .build(); + } + + /** + * 发送 OPT + * + * @return {@link ApiRestResult} + */ + @PostMapping("/otp") + @Lock(namespaces = "otp") + public ResponseEntity> send(@Validated SendOptRequest request) { + otpContextHelp.sendOtp(request.getTarget(), request.getType(), request.getChannel()); + return ResponseEntity.ok(ApiRestResult.ok()); + } + + /** + * 发送 OTP 请求 + */ + @Data + public static class SendOptRequest implements Serializable { + /** + * 发送场景 + */ + @Parameter(description = "type") + @NotNull(message = "发送场景不能为空") + private String type; + + /** + * 渠道 + */ + @Parameter(description = "channel") + @NotNull(message = "消息渠道不能为空") + private MessageNoticeChannel channel; + + /** + * 目标 + */ + @Parameter(description = "target") + private String target; + } + + /** + * 用户服务类 + */ + private final UserService userService; + + /** + * OTP + */ + private final OtpContextHelp otpContextHelp; + + /** + * UserIdpBindService + */ + private final UserIdpBindService userIdpBindService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/account/UserGroupController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/account/UserGroupController.java new file mode 100644 index 00000000..35ea410a --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/account/UserGroupController.java @@ -0,0 +1,230 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.account; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import com.google.common.collect.Lists; + +import cn.topiam.employee.audit.annotation.Audit; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.common.entity.account.UserGroupEntity; +import cn.topiam.employee.common.entity.account.query.UserGroupMemberListQuery; +import cn.topiam.employee.common.entity.account.query.UserListQuery; +import cn.topiam.employee.console.converter.account.UserGroupConverter; +import cn.topiam.employee.console.pojo.query.account.UserGroupListQuery; +import cn.topiam.employee.console.pojo.result.account.UserGroupListResult; +import cn.topiam.employee.console.pojo.result.account.UserGroupMemberListResult; +import cn.topiam.employee.console.pojo.result.account.UserGroupResult; +import cn.topiam.employee.console.pojo.save.account.UserCreateParam; +import cn.topiam.employee.console.pojo.save.account.UserGroupCreateParam; +import cn.topiam.employee.console.pojo.update.account.UserGroupUpdateParam; +import cn.topiam.employee.console.service.account.UserGroupService; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.AccountConstants.USER_GROUP_PATH; + +/** + * 用户组 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/11 19:18 + */ +@Validated +@AllArgsConstructor +@Tag(name = "用户分组") +@RestController +@RequestMapping(value = USER_GROUP_PATH, produces = MediaType.APPLICATION_JSON_VALUE) +public class UserGroupController { + + /** + * 获取分组列表 + * + * @param page {@link PageModel} + * @return {@link UserListQuery} + */ + @Operation(summary = "获取用户组列表") + @GetMapping(value = "/list") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getUserGroupList(PageModel page, + UserGroupListQuery query) { + Page list = userGroupService.getUserGroupList(page, query); + return ApiRestResult.> builder().result(list).build(); + } + + /** + * 创建分组 + * + * @param param {@link UserCreateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "创建用户组") + @Audit(type = EventType.CREATE_USER_GROUP) + @PostMapping(value = "/create") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult createUserGroup(@RequestBody @Validated UserGroupCreateParam param) { + return ApiRestResult. builder().result(userGroupService.createUserGroup(param)) + .build(); + } + + /** + * 更改用户分组 + * + * @param param {@link UserCreateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "修改用户组") + @Audit(type = EventType.UPDATE_USER_GROUP) + @PutMapping(value = "/update") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult updateUserGroup(@RequestBody @Validated UserGroupUpdateParam param) { + return ApiRestResult. builder().result(userGroupService.updateUserGroup(param)) + .build(); + } + + /** + * 删除分组 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "删除用户组") + @Audit(type = EventType.DELETE_USER_GROUP) + @DeleteMapping(value = "/delete/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult deleteUserGroup(@PathVariable(value = "id") String id) { + return ApiRestResult. builder().result(userGroupService.deleteUserGroup(id)) + .build(); + } + + /** + * 根据ID查询用户分组 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Operation(summary = "获取用户组信息") + @GetMapping(value = "/get/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getUserGroup(@PathVariable(value = "id") String id) { + UserGroupEntity entity = userGroupService.getUserGroup(Long.valueOf(id)); + UserGroupResult result = userGroupConverter.entityConvertToUserGroupResult(entity); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 添加分组用户 + * + * @param userIds {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Validated + @Operation(summary = "添加用户组成员") + @Audit(type = EventType.ADD_USER_GROUP_MEMBER) + @PostMapping(value = "/add_member/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult addMember(@PathVariable(value = "id") String id, + @NotNull(message = "成员ID不能为空") @Parameter(description = "成员ID") String[] userIds) { + return ApiRestResult. builder().result(userGroupService.addMember(id, userIds)) + .build(); + } + + /** + * 移除分组用户 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "移除用户组成员") + @Audit(type = EventType.REMOVE_USER_GROUP_MEMBER) + @DeleteMapping(value = "/remove_member/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult removeMember(@PathVariable(value = "id") String id, + @NotEmpty(message = "用户ID不能为空") @Parameter(description = "ID") String userId) { + return ApiRestResult. builder().result(userGroupService.removeMember(id, userId)) + .build(); + } + + /** + * 批量移除分组内用户 + * + * @param userIds {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Validated + @Operation(summary = "批量移除分组成员") + @Audit(type = EventType.REMOVE_USER_GROUP_MEMBER) + @DeleteMapping(value = "/batch/remove_member/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult batchRemoveMember(@PathVariable String id, + @NotNull(message = "用户ID集合不能为空") @Parameter(description = "ID集合") String[] userIds) { + return ApiRestResult. builder() + .result(userGroupService.batchRemoveMember(id, Lists.newArrayList(userIds))).build(); + } + + /** + * 获取分组用户 + * + * @param query {@link UserGroupMemberListQuery} 参数 + * @return {@link Boolean} + */ + @Operation(summary = "获取用户组成员") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + @GetMapping(value = "/{id}/member_list") + public ApiRestResult> getUserGroupMemberList(PageModel model, + UserGroupMemberListQuery query) { + return ApiRestResult.> builder() + .result(userGroupService.getUserGroupMemberList(model, query)).build(); + } + + /** + * 用户组数据映射 + */ + private final UserGroupConverter userGroupConverter; + /** + * 用户分组service + */ + private final UserGroupService userGroupService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/account/UserIdpBindController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/account/UserIdpBindController.java new file mode 100644 index 00000000..b865fe06 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/account/UserIdpBindController.java @@ -0,0 +1,42 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.account; + +import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.AccountConstants.USER_PATH; + +/** + * 用户身份提供商绑定 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/4 19:06 + */ +@Validated +@Tag(name = "用户身份提供商绑定关系") +@RestController +@AllArgsConstructor +@RequestMapping(value = USER_PATH + "/idp", produces = MediaType.APPLICATION_JSON_VALUE) +public class UserIdpBindController { + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/analysis/AnalysisController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/analysis/AnalysisController.java new file mode 100644 index 00000000..2357626b --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/analysis/AnalysisController.java @@ -0,0 +1,166 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.analysis; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import cn.topiam.employee.audit.enums.EventStatus; +import cn.topiam.employee.console.pojo.query.analysis.AnalysisQuery; +import cn.topiam.employee.console.pojo.result.analysis.AppVisitRankResult; +import cn.topiam.employee.console.pojo.result.analysis.AuthnHotProviderResult; +import cn.topiam.employee.console.pojo.result.analysis.AuthnQuantityResult; +import cn.topiam.employee.console.pojo.result.analysis.OverviewResult; +import cn.topiam.employee.console.service.analysis.AnalysisService; +import cn.topiam.employee.support.result.ApiRestResult; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.AnalysisConstants.ANALYSIS_GROUP_NAME; +import static cn.topiam.employee.common.constants.AnalysisConstants.ANALYSIS_PATH; + +/** + * 统计分析 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/10/13 22:00 + */ +@Validated +@Tag(name = ANALYSIS_GROUP_NAME) +@RestController +@RequestMapping(ANALYSIS_PATH) +public class AnalysisController { + + /** + * 概述 + * + * @return {@link OverviewResult} + */ + @GetMapping("/overview") + @Operation(summary = "概述") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult overview() { + return ApiRestResult.ok(analysisService.overview()); + } + + /** + * 认证量 + * + * @param query {@link AnalysisQuery} + * @return {@link AuthnQuantityResult} + */ + @GetMapping("/authn/quantity") + @Operation(summary = "认证量") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> authnQuantity(@Validated AnalysisQuery query) { + if (true) { + return ApiRestResult.ok(analysisService.authnQuantity(query)); + } + List list = new ArrayList<>(); + list.add(new AuthnQuantityResult("一月", 18L, EventStatus.SUCCESS.getDesc())); + list.add(new AuthnQuantityResult("二月", 28L, EventStatus.SUCCESS.getDesc())); + list.add(new AuthnQuantityResult("三月", 39L, EventStatus.SUCCESS.getDesc())); + list.add(new AuthnQuantityResult("四月", 81L, EventStatus.SUCCESS.getDesc())); + list.add(new AuthnQuantityResult("五月", 47L, EventStatus.SUCCESS.getDesc())); + list.add(new AuthnQuantityResult("六月", 20L, EventStatus.SUCCESS.getDesc())); + list.add(new AuthnQuantityResult("七月", 24L, EventStatus.SUCCESS.getDesc())); + list.add(new AuthnQuantityResult("八月", 35L, EventStatus.SUCCESS.getDesc())); + //失败 + list.add(new AuthnQuantityResult("一月", 12L, EventStatus.FAIL.getDesc())); + list.add(new AuthnQuantityResult("二月", 23L, EventStatus.FAIL.getDesc())); + list.add(new AuthnQuantityResult("三月", 34L, EventStatus.FAIL.getDesc())); + list.add(new AuthnQuantityResult("四月", 99L, EventStatus.FAIL.getDesc())); + list.add(new AuthnQuantityResult("五月", 52L, EventStatus.FAIL.getDesc())); + list.add(new AuthnQuantityResult("六月", 35L, EventStatus.FAIL.getDesc())); + list.add(new AuthnQuantityResult("七月", 37L, EventStatus.FAIL.getDesc())); + list.add(new AuthnQuantityResult("八月", 42L, EventStatus.FAIL.getDesc())); + return ApiRestResult.ok(list); + } + + /** + * 热门认证提供商 + * + * @return {@link List} + */ + @GetMapping("/authn/hot_provider") + @Operation(summary = "热门认证提供商") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> authnHotProvider(@Validated AnalysisQuery query) { + ArrayList list = new ArrayList<>() { + { + add(new AuthnHotProviderResult("微信扫码登录", 1000L)); + add(new AuthnHotProviderResult("钉钉扫码登录", 100L)); + add(new AuthnHotProviderResult("企业微信", 99L)); + add(new AuthnHotProviderResult("QQ", 88L)); + add(new AuthnHotProviderResult("Github", 77L)); + add(new AuthnHotProviderResult("支付宝扫码认证", 66L)); + add(new AuthnHotProviderResult("LDAP", 55L)); + add(new AuthnHotProviderResult("微博", 10L)); + } + }; + return ApiRestResult.ok(list); + } + + /** + * 登录区域 + */ + @GetMapping("/authn/zone") + @Operation(summary = "登录区域") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public void authnZone(@Validated AnalysisQuery query) { + + } + + /** + * 访问应用排名 + * + * @param query {@link AnalysisQuery} + * @return {@link AuthnQuantityResult} + */ + @GetMapping("/app/visit_rank") + @Operation(summary = "访问应用排名") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> appVisitRank(@Validated AnalysisQuery query) { + if (true) { + return ApiRestResult.ok(analysisService.appVisitRank(query)); + } + List list = new ArrayList<>(); + list.add(new AppVisitRankResult("阿里云用户", 145L)); + list.add(new AppVisitRankResult("腾讯云用户", 61L)); + list.add(new AppVisitRankResult("华为云", 52L)); + list.add(new AppVisitRankResult("百度云用户", 48L)); + list.add(new AppVisitRankResult("阿里云角色", 38L)); + list.add(new AppVisitRankResult("百度云角色", 28L)); + list.add(new AppVisitRankResult("腾讯云角色", 22L)); + list.add(new AppVisitRankResult("OIDC", 10L)); + return ApiRestResult.ok(list); + } + + private final AnalysisService analysisService; + + public AnalysisController(AnalysisService analysisService) { + this.analysisService = analysisService; + } + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/analysis/package-info.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/analysis/package-info.java new file mode 100644 index 00000000..91f42dfc --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/analysis/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.analysis; \ No newline at end of file diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppAccessPolicyController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppAccessPolicyController.java new file mode 100644 index 00000000..ce5eac37 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppAccessPolicyController.java @@ -0,0 +1,112 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.app; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import cn.topiam.employee.audit.annotation.Audit; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.common.entity.app.query.AppAccessPolicyQuery; +import cn.topiam.employee.console.pojo.result.app.AppAccessPolicyResult; +import cn.topiam.employee.console.pojo.result.app.AppAccountListResult; +import cn.topiam.employee.console.pojo.save.app.AppAccessPolicyCreateParam; +import cn.topiam.employee.console.pojo.save.app.AppAccountCreateParam; +import cn.topiam.employee.console.service.app.AppAccessPolicyService; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.AppConstants.APP_PATH; + +/** + * 应用访问授权策略 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/4 19:58 + */ +@Validated +@Tag(name = "应用访问授权策略") +@RestController +@AllArgsConstructor +@RequestMapping(value = APP_PATH + "/access_policy", produces = MediaType.APPLICATION_JSON_VALUE) +public class AppAccessPolicyController { + + /** + * 获取应用访问授权策略列表 + * + * @param page {@link PageModel} + * @param query {@link AppAccessPolicyQuery} + * @return {@link AppAccountListResult} + */ + @Operation(summary = "获取应用访问授权策略列表") + @GetMapping(value = "/list") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getAppAccessPolicyList(PageModel page, + @Validated AppAccessPolicyQuery query) { + return ApiRestResult.> builder() + .result(appAccessPolicyService.getAppAccessPolicyList(page, query)).build(); + } + + /** + * 创建应用访问授权策略 + * + * @param param {@link AppAccountCreateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "创建应用访问授权策略") + @Audit(type = EventType.APP_AUTHORIZATION) + @PostMapping(value = "/create") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult createAppAccessPolicy(@RequestBody @Validated AppAccessPolicyCreateParam param) { + return ApiRestResult. builder() + .result(appAccessPolicyService.createAppAccessPolicy(param)).build(); + } + + /** + * 删除应用访问授权策略 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "删除应用访问授权策略") + @Audit(type = EventType.APP_DELETE_ACCESS_POLICY) + @DeleteMapping(value = "/delete/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult deleteAppAccessPolicy(@PathVariable(value = "id") String id) { + return ApiRestResult. builder() + .result(appAccessPolicyService.deleteAppAccessPolicy(id)).build(); + } + + /** + * AppPolicyService + */ + private final AppAccessPolicyService appAccessPolicyService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppAccountController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppAccountController.java new file mode 100644 index 00000000..10d7c4bd --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppAccountController.java @@ -0,0 +1,111 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.app; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import cn.topiam.employee.audit.annotation.Audit; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.common.entity.app.query.AppAccountQuery; +import cn.topiam.employee.console.pojo.result.app.AppAccountListResult; +import cn.topiam.employee.console.pojo.save.app.AppAccountCreateParam; +import cn.topiam.employee.console.service.app.AppAccountService; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.AppConstants.APP_PATH; + +/** + * 应用账户资源 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/4 19:06 + */ +@Validated +@Tag(name = "应用账户") +@RestController +@AllArgsConstructor +@RequestMapping(value = APP_PATH + "/account", produces = MediaType.APPLICATION_JSON_VALUE) +public class AppAccountController { + + /** + * AppAccountService + */ + private final AppAccountService appAccountService; + + /** + * 获取应用账户列表 + * + * @param page {@link PageModel} + * @param query {@link AppAccountQuery} + * @return {@link AppAccountListResult} + */ + @Operation(summary = "获取应用账户列表") + @GetMapping(value = "/list") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getAppAccountList(PageModel page, + @Validated AppAccountQuery query) { + return ApiRestResult.> builder() + .result(appAccountService.getAppAccountList(page, query)).build(); + } + + /** + * 创建应用账户 + * + * @param param {@link AppAccountCreateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "创建应用账户") + @Audit(type = EventType.ADD_APP_ACCOUNT) + @PostMapping(value = "/create") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult createAppAccount(@RequestBody @Validated AppAccountCreateParam param) { + return ApiRestResult. builder().result(appAccountService.createAppAccount(param)) + .build(); + } + + /** + * 删除应用账户 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "删除应用账户") + @Audit(type = EventType.DELETE_APP_ACCOUNT) + @DeleteMapping(value = "/delete/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult deleteAppAccount(@PathVariable(value = "id") String id) { + return ApiRestResult. builder().result(appAccountService.deleteAppAccount(id)) + .build(); + } + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppCertController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppCertController.java new file mode 100644 index 00000000..5d7f082d --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppCertController.java @@ -0,0 +1,69 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.app; + +import java.util.List; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import cn.topiam.employee.application.saml2.model.AppSaml2StandardConfigGetResult; +import cn.topiam.employee.console.pojo.query.app.AppCertQuery; +import cn.topiam.employee.console.pojo.result.app.AppCertListResult; +import cn.topiam.employee.console.service.app.AppCertService; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.AppConstants.APP_PATH; + +/** + * 应用证书 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/31 21:38 + */ +@Validated +@Tag(name = "应用证书") +@RestController +@AllArgsConstructor +@RequestMapping(value = APP_PATH + "/cert", produces = MediaType.APPLICATION_JSON_VALUE) +public class AppCertController { + + /** + * 获取应用证书列表 + * + * @param query {@link AppCertQuery} + * @return {@link AppSaml2StandardConfigGetResult} + */ + @Operation(summary = "获取应用证书列表") + @GetMapping(value = "/list") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getAppCertificateListResult(@Validated AppCertQuery query) { + List list = appCertService.getAppCertListResult(query); + return ApiRestResult.> builder().result(list).build(); + } + + private final AppCertService appCertService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppController.java new file mode 100644 index 00000000..cfdc6502 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppController.java @@ -0,0 +1,207 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.app; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import cn.topiam.employee.application.saml2.model.AppSaml2StandardConfigGetResult; +import cn.topiam.employee.audit.annotation.Audit; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.console.pojo.query.app.AppQuery; +import cn.topiam.employee.console.pojo.result.app.AppCreateResult; +import cn.topiam.employee.console.pojo.result.app.AppGetResult; +import cn.topiam.employee.console.pojo.result.app.AppListResult; +import cn.topiam.employee.console.pojo.save.app.AppCreateParam; +import cn.topiam.employee.console.pojo.update.app.AppSaveConfigParam; +import cn.topiam.employee.console.pojo.update.app.AppUpdateParam; +import cn.topiam.employee.console.service.app.AppService; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.AppConstants.APP_PATH; + +/** + * 应用管理 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/24 22:35 + */ +@Validated +@Tag(name = "应用管理") +@RestController +@AllArgsConstructor +@RequestMapping(value = APP_PATH, produces = MediaType.APPLICATION_JSON_VALUE) +public class AppController { + + /** + * 获取应用列表 + * + * @param page {@link PageModel} + * @return {@link AppQuery} + */ + @Operation(summary = "获取应用列表") + @GetMapping(value = "/list") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getAppList(PageModel page, AppQuery query) { + Page list = appService.getAppList(page, query); + return ApiRestResult.> builder().result(list).build(); + } + + /** + * 创建应用 + * + * @param param {@link AppCreateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "创建应用") + @Audit(type = EventType.ADD_APP) + @PostMapping(value = "/create") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult createApp(@RequestBody @Validated AppCreateParam param) { + return ApiRestResult. builder().result(appService.createApp(param)) + .build(); + } + + /** + * 修改应用 + * + * @param param {@link AppUpdateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "修改应用") + @Audit(type = EventType.UPDATE_APP) + @PutMapping(value = "/update") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult updateApp(@RequestBody @Validated AppUpdateParam param) { + return ApiRestResult. builder().result(appService.updateApp(param)).build(); + } + + /** + * 更新应用配置 + * + * @param param {@link AppSaveConfigParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "保存应用配置") + @Audit(type = EventType.UPDATE_APP) + @PutMapping(value = "/save/config") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult saveAppConfig(@RequestBody @Validated AppSaveConfigParam param) { + return ApiRestResult. builder().result(appService.saveAppConfig(param)).build(); + } + + /** + * 获取应用配置 + * + * @param appId {@link String} + * @return {@link AppSaml2StandardConfigGetResult} + */ + @Operation(summary = "获取应用配置") + @GetMapping(value = "/get/config/{appId}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getAppConfig(@PathVariable String appId) { + Object config = appService.getAppConfig(appId); + return ApiRestResult.builder().result(config).build(); + } + + /** + * 删除应用 + * + * @param id {@link Long} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "删除应用") + @Audit(type = EventType.DELETE_APP) + @DeleteMapping(value = "/delete/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult deleteApp(@PathVariable(value = "id") String id) { + return ApiRestResult. builder().result(appService.deleteApp(Long.valueOf(id))) + .build(); + } + + /** + * 获取应用信息 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Operation(summary = "获取应用信息") + @GetMapping(value = "/get/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getApp(@PathVariable(value = "id") String id) { + AppGetResult result = appService.getApp(Long.valueOf(id)); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 启用应用 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "启用应用") + @Audit(type = EventType.ENABLE_APP) + @PutMapping(value = "/enable/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult enableIdentitySource(@PathVariable(value = "id") String id) { + boolean result = appService.enableApp(id); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 禁用应用 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "禁用应用") + @Audit(type = EventType.DISABLE_APP) + @PutMapping(value = "/disable/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult disableIdentitySource(@PathVariable(value = "id") String id) { + boolean result = appService.disableApp(id); + return ApiRestResult. builder().result(result).build(); + } + + /** + * ApplicationService + */ + private final AppService appService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppPermissionActionController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppPermissionActionController.java new file mode 100644 index 00000000..4b06b32f --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppPermissionActionController.java @@ -0,0 +1,76 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.app; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import cn.topiam.employee.console.pojo.query.app.AppPermissionActionListQuery; +import cn.topiam.employee.console.pojo.result.app.AppPermissionActionListResult; +import cn.topiam.employee.console.service.app.AppPermissionActionService; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.RequiredArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.AppConstants.APP_PATH; + +/** + * 应用权限-权限 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/26 20:28 + */ +@RequiredArgsConstructor +@Validated +@Tag(name = "应用权限-权限项") +@RequestMapping(value = APP_PATH + + "/permission/action", produces = MediaType.APPLICATION_JSON_VALUE) +@RestController +public class AppPermissionActionController { + + /** + * logger + */ + private final Logger logger = LoggerFactory.getLogger(AppPermissionActionController.class); + + /** + * 获取所有权限 + * + * @return {@link AppPermissionActionListResult} + */ + @Operation(summary = "获取权限项列表") + @GetMapping(value = "/list") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getPermissionActionList(@Validated AppPermissionActionListQuery query) { + List list = appPermissionActionService + .getPermissionActionList(query); + return ApiRestResult.> builder().result(list).build(); + } + + private final AppPermissionActionService appPermissionActionService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppPermissionPolicyController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppPermissionPolicyController.java new file mode 100644 index 00000000..208f77b7 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppPermissionPolicyController.java @@ -0,0 +1,142 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.app; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import cn.topiam.employee.common.entity.app.query.AppPolicyQuery; +import cn.topiam.employee.console.pojo.result.app.AppPermissionPolicyGetResult; +import cn.topiam.employee.console.pojo.result.app.AppPermissionPolicyListResult; +import cn.topiam.employee.console.pojo.result.app.AppPermissionRoleListResult; +import cn.topiam.employee.console.pojo.save.app.AppPermissionPolicyCreateParam; +import cn.topiam.employee.console.pojo.save.app.AppPermissionRoleCreateParam; +import cn.topiam.employee.console.pojo.update.app.AppPermissionPolicyUpdateParam; +import cn.topiam.employee.console.pojo.update.app.PermissionRoleUpdateParam; +import cn.topiam.employee.console.service.app.AppPermissionPolicyService; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.RequiredArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.AppConstants.APP_PATH; + +/** + * 应用权限 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/7/12 22:30 + */ +@Validated +@Tag(name = "应用权限-授权策略") +@RequestMapping(value = APP_PATH + + "/permission/policy", produces = MediaType.APPLICATION_JSON_VALUE) +@RestController +@RequiredArgsConstructor +public class AppPermissionPolicyController { + + /** + * 获取所有策略(分页) + * + * @param page {@link PageModel} + * @return {@link AppPermissionRoleListResult} + */ + @Operation(summary = "获取策略列表") + @GetMapping(value = "/list") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getPermissionPolicyList(PageModel page, + @Validated AppPolicyQuery query) { + Page result = permissionPolicyService + .getPermissionPolicyList(page, query); + return ApiRestResult.> builder().result(result).build(); + } + + /** + * 创建资源 + * + * @param param {@link AppPermissionRoleCreateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "创建资源") + @PostMapping(value = "/create") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult createPolicy(@Validated @RequestBody AppPermissionPolicyCreateParam param) { + return ApiRestResult. builder() + .result(permissionPolicyService.createPermissionPolicy(param)).build(); + } + + /** + * 修改资源 + * + * @param param {@link PermissionRoleUpdateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "修改资源") + @PutMapping(value = "/update/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult updatePolicy(@Validated AppPermissionPolicyUpdateParam param) { + return ApiRestResult. builder() + .result(permissionPolicyService.updatePermissionPolicy(param)).build(); + } + + /** + * 删除资源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "删除资源") + @DeleteMapping(value = "/delete/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult deletePermissionPolicy(@PathVariable(value = "id") String id) { + return ApiRestResult. builder() + .result(permissionPolicyService.deletePermissionPolicy(id)).build(); + } + + /** + * 获取资源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "获取资源信息") + @GetMapping(value = "/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getPermissionPolicy(@PathVariable(value = "id") String id) { + //返回 + return ApiRestResult. builder() + .result(permissionPolicyService.getPermissionPolicy(id)).build(); + } + + private final AppPermissionPolicyService permissionPolicyService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppPermissionResourceController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppPermissionResourceController.java new file mode 100644 index 00000000..68ba1ed2 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppPermissionResourceController.java @@ -0,0 +1,213 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.app; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import cn.topiam.employee.audit.annotation.Audit; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.common.enums.CheckValidityType; +import cn.topiam.employee.console.pojo.query.app.AppResourceListQuery; +import cn.topiam.employee.console.pojo.result.app.AppPermissionResourceGetResult; +import cn.topiam.employee.console.pojo.result.app.AppPermissionResourceListResult; +import cn.topiam.employee.console.pojo.result.app.AppPermissionRoleListResult; +import cn.topiam.employee.console.pojo.save.app.AppPermissionResourceCreateParam; +import cn.topiam.employee.console.pojo.save.app.AppPermissionRoleCreateParam; +import cn.topiam.employee.console.pojo.update.app.AppPermissionResourceUpdateParam; +import cn.topiam.employee.console.pojo.update.app.PermissionRoleUpdateParam; +import cn.topiam.employee.console.service.app.AppPermissionResourceService; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.RequiredArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.AppConstants.APP_PATH; + +/** + * 应用权限 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/7/12 22:30 + */ +@Validated +@Tag(name = "应用权限-资源") +@RequestMapping(value = APP_PATH + + "/permission/resource", produces = MediaType.APPLICATION_JSON_VALUE) +@RestController +@RequiredArgsConstructor +public class AppPermissionResourceController { + + /** + * logger + */ + private final Logger logger = LoggerFactory.getLogger(AppPermissionResourceController.class); + + /** + * 获取所有资源(分页) + * + * @param page {@link PageModel} + * @return {@link AppPermissionRoleListResult} + */ + @Operation(summary = "获取资源列表") + @GetMapping(value = "/list") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getPermissionResourceList(PageModel page, + @Validated AppResourceListQuery query) { + Page result = appPermissionResourceService + .getPermissionResourceList(page, query); + return ApiRestResult.> builder().result(result) + .build(); + } + + /** + * 创建资源 + * + * @param param {@link AppPermissionRoleCreateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "创建资源") + @Audit(type = EventType.SAVE_APP_PERMISSION_RESOURCE) + @PostMapping(value = "/create") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult createResource(@Validated @RequestBody AppPermissionResourceCreateParam param) { + return ApiRestResult. builder() + .result(appPermissionResourceService.createPermissionResource(param)).build(); + } + + /** + * 修改资源 + * + * @param param {@link PermissionRoleUpdateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "修改资源") + @Audit(type = EventType.UPDATE_APP_PERMISSION_RESOURCE) + @PutMapping(value = "/update") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult updateResource(@RequestBody @Validated AppPermissionResourceUpdateParam param) { + return ApiRestResult. builder() + .result(appPermissionResourceService.updatePermissionResource(param)).build(); + } + + /** + * 删除资源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "删除资源") + @Audit(type = EventType.DELETE_APP_PERMISSION_RESOURCE) + @DeleteMapping(value = "/delete/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult deletePermissionResource(@PathVariable(value = "id") String id) { + return ApiRestResult. builder() + .result(appPermissionResourceService.deletePermissionResource(id)).build(); + } + + /** + * 获取资源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Operation(summary = "获取资源信息") + @GetMapping(value = "/get/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getPermissionResource(@PathVariable(value = "id") String id) { + //返回 + return ApiRestResult. builder() + .result(appPermissionResourceService.getPermissionResource(id)).build(); + } + + /** + * 启用资源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "启用资源") + @Audit(type = EventType.ENABLE_APP_PERMISSION_RESOURCE) + @PutMapping(value = "/enable/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult enableOrganization(@PathVariable(value = "id") Long id) { + return ApiRestResult. builder() + .result(appPermissionResourceService.updateStatus(id, Boolean.TRUE)).build(); + } + + /** + * 禁用资源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "禁用资源") + @Audit(type = EventType.DISABLE_APP_PERMISSION_RESOURCE) + @PutMapping(value = "/disable/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult disableOrganization(@PathVariable(value = "id") Long id) { + return ApiRestResult. builder() + .result(appPermissionResourceService.updateStatus(id, Boolean.FALSE)).build(); + } + + /** + * 参数有效性验证 + * + * @return {@link Boolean} + */ + @Operation(summary = "参数有效性验证") + @GetMapping(value = "/param_check") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult resourceParamCheck(@Parameter(description = "验证类型") @NotNull(message = "验证类型不能为空") CheckValidityType type, + @Parameter(description = "值") @NotEmpty(message = "验证值不能为空") String value, + @Parameter(description = "应用ID") @NotNull(message = "应用ID不能为空") Long appId, + @Parameter(description = "ID") Long id) { + Boolean result = appPermissionResourceService.permissionResourceParamCheck(type, value, + appId, id); + //返回 + return ApiRestResult. builder().result(result).build(); + } + + /** + * 资源服务类 + */ + private final AppPermissionResourceService appPermissionResourceService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppPermissionRoleController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppPermissionRoleController.java new file mode 100644 index 00000000..6bbafef9 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppPermissionRoleController.java @@ -0,0 +1,205 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.app; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import cn.topiam.employee.audit.annotation.Audit; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.common.enums.CheckValidityType; +import cn.topiam.employee.console.pojo.query.app.AppPermissionRoleListQuery; +import cn.topiam.employee.console.pojo.result.app.AppPermissionRoleListResult; +import cn.topiam.employee.console.pojo.result.app.AppPermissionRoleResult; +import cn.topiam.employee.console.pojo.save.app.AppPermissionRoleCreateParam; +import cn.topiam.employee.console.pojo.update.app.PermissionRoleUpdateParam; +import cn.topiam.employee.console.service.app.AppPermissionRoleService; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.RequiredArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.AppConstants.APP_PATH; + +/** + * 应用角色 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/26 20:28 + */ +@RequiredArgsConstructor +@Validated +@Tag(name = "应用权限-角色") +@RequestMapping(value = APP_PATH + "/permission/role", produces = MediaType.APPLICATION_JSON_VALUE) +@RestController +public class AppPermissionRoleController { + + /** + * logger + */ + private final Logger logger = LoggerFactory.getLogger(AppPermissionRoleController.class); + + /** + * 获取所有角色(分页) + * + * @param page {@link PageModel} + * @return {@link AppPermissionRoleListResult} + */ + @Operation(summary = "获取角色列表") + @GetMapping(value = "/list") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getPermissionRoleList(PageModel page, + @Validated AppPermissionRoleListQuery query) { + Page result = appPermissionRoleService + .getPermissionRoleList(page, query); + return ApiRestResult.> builder().result(result).build(); + } + + /** + * 创建角色 + * + * @param param {@link AppPermissionRoleCreateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "创建角色") + @Audit(type = EventType.SAVE_APP_PERMISSION_ROLE) + @PostMapping(value = "/create") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult createPermissionRole(@Validated @RequestBody AppPermissionRoleCreateParam param) { + return ApiRestResult. builder() + .result(appPermissionRoleService.createPermissionRole(param)).build(); + } + + /** + * 修改角色 + * + * @param param {@link PermissionRoleUpdateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "修改角色") + @Audit(type = EventType.UPDATE_APP_PERMISSION_ROLE) + @PutMapping(value = "/update") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult updatePermissionRole(@RequestBody @Validated PermissionRoleUpdateParam param) { + return ApiRestResult. builder() + .result(appPermissionRoleService.updatePermissionRole(param)).build(); + } + + /** + * 删除角色 + * + * @param ids {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "删除角色") + @Audit(type = EventType.DELETE_APP_PERMISSION_ROLE) + @DeleteMapping(value = "/delete/{ids}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult deletePermissionRole(@PathVariable(value = "ids") String ids) { + return ApiRestResult. builder() + .result(appPermissionRoleService.deletePermissionRole(ids)).build(); + } + + /** + * 获取角色 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Operation(summary = "获取角色信息") + @GetMapping(value = "/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getPermissionRole(@PathVariable(value = "id") Long id) { + AppPermissionRoleResult details = appPermissionRoleService.getPermissionRole(id); + //返回 + return ApiRestResult. builder().result(details).build(); + } + + /** + * 启用角色 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "启用角色") + @PutMapping(value = "/enable/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult enablePermissionRole(@PathVariable(value = "id") String id) { + Boolean result = appPermissionRoleService.updatePermissionRoleStatus(id, Boolean.TRUE); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 禁用角色 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "禁用角色") + @PutMapping(value = "/disable/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult disablePermissionRole(@PathVariable(value = "id") String id) { + Boolean result = appPermissionRoleService.updatePermissionRoleStatus(id, Boolean.FALSE); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 参数有效性验证 + * + * @return {@link Boolean} + */ + @Operation(summary = "参数有效性验证") + @GetMapping(value = "/param_check") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult permissionRoleParamCheck(@Parameter(description = "验证类型") @NotNull(message = "验证类型不能为空") CheckValidityType type, + @Parameter(description = "值") @NotEmpty(message = "验证值不能为空") String value, + @Parameter(description = "应用ID") @NotNull(message = "应用ID不能为空") Long appId, + @Parameter(description = "ID") Long id) { + Boolean result = appPermissionRoleService.permissionRoleParamCheck(type, value, appId, id); + //返回 + return ApiRestResult. builder().result(result).build(); + } + + /** + * 角色服务类 + */ + private final AppPermissionRoleService appPermissionRoleService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppSaml2Controller.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppSaml2Controller.java new file mode 100644 index 00000000..92c276a7 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppSaml2Controller.java @@ -0,0 +1,105 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.app; + +import java.io.IOException; + +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import cn.topiam.employee.console.pojo.result.app.ParseSaml2MetadataResult; +import cn.topiam.employee.console.service.app.AppSaml2Service; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.AppConstants.APP_PATH; + +/** + * SAML2 应用配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/31 21:38 + */ +@Validated +@Tag(name = "SAML2 应用元数据解析") +@RestController +@AllArgsConstructor +@RequestMapping(value = APP_PATH + "/saml2", produces = MediaType.APPLICATION_JSON_VALUE) +public class AppSaml2Controller { + + /** + * 解析SAML metadata + * + * @param metadataUrl {@link String} + * @return {@link Boolean} + */ + @Lock + @Operation(summary = "通过 Metadata URL 解析元数据") + @PostMapping(value = "/parse/metadata_url") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult parseSaml2MetadataUrl(@Parameter(description = "元数据 URL") @RequestParam String metadataUrl) { + ParseSaml2MetadataResult result = appSaml2Service.parseSaml2MetadataUrl(metadataUrl); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 解析SAML metadata + * + * @param metadataFile {@link MultipartFile} + * @return {@link Boolean} + */ + @Lock + @Operation(summary = "通过 Metadata File 解析元数据") + @PostMapping(value = "/parse/metadata_file", consumes = { MediaType.MULTIPART_FORM_DATA_VALUE }, produces = { MediaType.APPLICATION_JSON_VALUE }) + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult parseSaml2MetadataFile(@RequestPart(value = "file") @Parameter(description = "元数据文件") final MultipartFile metadataFile) throws IOException { + ParseSaml2MetadataResult result = appSaml2Service + .parseSaml2Metadata(metadataFile.getInputStream()); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 下载 IDP SAML2 元数据 + * + * @param appId {@link String} + */ + @Lock + @Operation(summary = "下载 IDP SAML2 元数据") + @GetMapping(value = "/download/idp_metadata_file") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public void downloadSaml2IdpMetadataFile(@Valid @Parameter(description = "应用ID") @NotBlank(message = "应用ID不能为空") @RequestParam(value = "appId", required = false) String appId) throws IOException { + appSaml2Service.downloadSaml2IdpMetadataFile(appId); + } + + /** + * AppSaml2Service + */ + private final AppSaml2Service appSaml2Service; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppTemplateController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppTemplateController.java new file mode 100644 index 00000000..f205d274 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/app/AppTemplateController.java @@ -0,0 +1,87 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.app; + +import java.util.List; +import java.util.Map; + +import javax.validation.constraints.NotEmpty; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import cn.topiam.employee.common.enums.app.AppType; +import cn.topiam.employee.console.pojo.result.app.AppTemplateResult; +import cn.topiam.employee.console.service.app.AppTemplateService; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.RequiredArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.AppConstants.APP_PATH; + +/** + * 应用模板 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/9/27 19:28 + */ +@Validated +@Tag(name = "应用模板") +@RestController +@RequiredArgsConstructor +@RequestMapping(value = APP_PATH + "/template", produces = MediaType.APPLICATION_JSON_VALUE) +public class AppTemplateController { + + /** + * 获取模板列表 + * + * @return {@link AppTemplateResult} + */ + @Operation(summary = "模板列表") + @GetMapping(value = "/list") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getAppTemplateList(@Parameter(description = "模板类型") @RequestParam(value = "type", required = false) String type, + @RequestParam(value = "name", required = false) String name) { + return ApiRestResult.ok(templateService.getAppTemplateList(AppType.getType(type), name)); + } + + /** + * 模板表单架构 + * + * @return {@link AppTemplateResult} + */ + @Operation(summary = "模板表单架构") + @GetMapping(value = "/form_schema") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getAppTemplateFormSchema(@Validated @Parameter(description = "模板编码") @NotEmpty(message = "模板编码不能为空") @RequestParam(value = "code", required = false) String code) { + return ApiRestResult.ok(templateService.getAppTemplateFormSchema(code)); + } + + /** + * ApplicationTemplateService + */ + private final AppTemplateService templateService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/authentication/IdentityProviderController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/authentication/IdentityProviderController.java new file mode 100644 index 00000000..6aff3b16 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/authentication/IdentityProviderController.java @@ -0,0 +1,178 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.authentication; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import cn.topiam.employee.audit.annotation.Audit; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.common.constants.AuthenticationConstants; +import cn.topiam.employee.console.pojo.query.authentication.IdentityProviderListQuery; +import cn.topiam.employee.console.pojo.result.authentication.IdentityProviderCreateResult; +import cn.topiam.employee.console.pojo.result.authentication.IdentityProviderListResult; +import cn.topiam.employee.console.pojo.result.authentication.IdentityProviderResult; +import cn.topiam.employee.console.pojo.save.authentication.IdentityProviderCreateParam; +import cn.topiam.employee.console.pojo.update.authentication.IdpUpdateParam; +import cn.topiam.employee.console.service.authentication.IdentityProviderService; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 身份提供商 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/11 19:18 + */ +@Tag(name = "身份提供商") +@Validated +@RestController +@RequestMapping(value = AuthenticationConstants.AUTHENTICATION_PATH + + "/idp", produces = MediaType.APPLICATION_JSON_VALUE) +@AllArgsConstructor +public class IdentityProviderController { + + /** + * 获取列表 + * + * @return {@link IdentityProviderListResult} + */ + @Operation(summary = "提供商列表") + @GetMapping(value = "/list") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getIdentityProviderList(PageModel pageModel, + IdentityProviderListQuery query) { + Page results = identityProviderService + .getIdentityProviderList(pageModel, query); + return ApiRestResult.> builder().result(results).build(); + } + + /** + * 创建提供商 + * + * @param param {@link IdentityProviderCreateParam} + * @return {@link IdentityProviderCreateResult} + */ + @Lock + @Preview + @Operation(summary = "创建提供商") + @Audit(type = EventType.ADD_IDP) + @PostMapping(value = "/create") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult createIdentityProvider(@RequestBody @Validated IdentityProviderCreateParam param) { + IdentityProviderCreateResult result = identityProviderService.createIdp(param); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 修改提供商 + * + * @param param {@link IdentityProviderCreateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "修改提供商") + @Audit(type = EventType.UPDATE_IDP) + @PutMapping(value = "/update") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult updateIdentityProvider(@RequestBody @Validated IdpUpdateParam param) { + boolean success = identityProviderService.updateIdentityProvider(param); + return ApiRestResult. builder().result(success).build(); + } + + /** + * 详情 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Operation(summary = "获取提供商信息") + @GetMapping(value = "/get/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getIdentityProvider(@PathVariable(value = "id") String id) { + IdentityProviderResult result = identityProviderService.getIdentityProvider(id); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 启用提供商 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "启用提供商") + @Audit(type = EventType.ENABLE_IDP) + @PutMapping(value = "/enable/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult enableIdentityProvider(@PathVariable(value = "id") String id) { + boolean result = identityProviderService.updateIdentityProviderStatus(id, true); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 禁用提供商 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "禁用提供商") + @Audit(type = EventType.DISABLE_IDP) + @PutMapping(value = "/disable/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult disableIdentityProvider(@PathVariable(value = "id") String id) { + boolean result = identityProviderService.updateIdentityProviderStatus(id, false); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 删除提供商 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "删除提供商") + @Audit(type = EventType.DELETE_IDP) + @DeleteMapping(value = "/delete/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult deleteIdentityProvider(@PathVariable(value = "id") String id) { + boolean result = identityProviderService.deleteIdentityProvider(id); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 身份提供商服务 + */ + private final IdentityProviderService identityProviderService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/identitysource/IdentitySourceController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/identitysource/IdentitySourceController.java new file mode 100644 index 00000000..83ba11ef --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/identitysource/IdentitySourceController.java @@ -0,0 +1,245 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.identitysource; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import cn.topiam.employee.audit.annotation.Audit; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.common.entity.identitysource.IdentitySourceEntity; +import cn.topiam.employee.console.converter.identitysource.IdentitySourceConverter; +import cn.topiam.employee.console.pojo.other.IdentitySourceConfigValidatorParam; +import cn.topiam.employee.console.pojo.query.identity.IdentitySourceListQuery; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceConfigGetResult; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceGetResult; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceListResult; +import cn.topiam.employee.console.pojo.save.identitysource.IdentitySourceConfigSaveParam; +import cn.topiam.employee.console.pojo.save.identitysource.IdentitySourceCreateParam; +import cn.topiam.employee.console.pojo.save.identitysource.IdentitySourceCreateResult; +import cn.topiam.employee.console.pojo.update.identity.IdentitySourceUpdateParam; +import cn.topiam.employee.console.service.identitysource.IdentitySourceService; +import cn.topiam.employee.identitysource.core.event.IdentitySourceEventUtils; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.AccountConstants.IDENTITY_SOURCE_PATH; + +/** + * 身份源管理 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/11 19:18 + */ +@Validated +@Tag(name = "身份源管理") +@RestController +@AllArgsConstructor +@RequestMapping(value = IDENTITY_SOURCE_PATH, produces = MediaType.APPLICATION_JSON_VALUE) +public class IdentitySourceController { + + /** + * 获取列表 + * + * @return {@link IdentitySourceListResult} + */ + @Operation(summary = "身份源列表") + @GetMapping(value = "/list") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getIdentitySourceList(PageModel pageModel, + IdentitySourceListQuery query) { + Page results = identitySourceService.getIdentitySourceList(query, + pageModel); + return ApiRestResult.> builder().result(results).build(); + } + + /** + * 创建身份源 + * + * @param param {@link IdentitySourceCreateParam} + * @return {@link IdentitySourceCreateResult} + */ + @Lock + @Preview + @Operation(summary = "创建身份源") + @Audit(type = EventType.CREATE_IDENTITY_RESOURCE) + @PostMapping(value = "/create") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult createIdentitySource(@RequestBody @Validated IdentitySourceCreateParam param) { + IdentitySourceCreateResult result = identitySourceService.createIdentitySource(param); + IdentitySourceEventUtils.register(result.getId()); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 修改身份源 + * + * @param param {@link IdentitySourceCreateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "修改身份源") + @Audit(type = EventType.UPDATE_IDENTITY_RESOURCE) + @PutMapping(value = "/update") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult updateIdentitySource(@RequestBody @Validated IdentitySourceUpdateParam param) { + boolean success = identitySourceService.updateIdentitySource(param); + //注册 + IdentitySourceEventUtils.register(param.getId()); + return ApiRestResult. builder().result(success).build(); + } + + /** + * 保存身份源配置 + * + * @param param {@link IdentitySourceCreateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "保存身份源配置") + @PutMapping(value = "/save/config") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult saveIdentitySourceConfig(@RequestBody @Validated IdentitySourceConfigSaveParam param) { + boolean success = identitySourceService.saveIdentitySourceConfig(param); + //注册 + IdentitySourceEventUtils.register(param.getId()); + return ApiRestResult. builder().result(success).build(); + } + + /** + * 获取身份源配置 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Operation(summary = "获取身份源配置") + @GetMapping(value = "/get/config/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getIdentitySourceConfig(@PathVariable(value = "id") String id) { + IdentitySourceEntity entity = identitySourceService.getIdentitySource(id); + IdentitySourceConfigGetResult result = identitySourceConverter + .entityConverterToIdentitySourceConfigGetResult(entity); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 获取认证源信息 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Operation(summary = "获取认证源") + @GetMapping(value = "/get/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getIdentitySource(@PathVariable(value = "id") String id) { + IdentitySourceEntity entity = identitySourceService.getIdentitySource(id); + IdentitySourceGetResult result = identitySourceConverter + .entityConverterToIdentitySourceGetResult(entity); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 启用身份源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "启用身份源") + @Audit(type = EventType.ENABLE_IDENTITY_RESOURCE) + @PutMapping(value = "/enable/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult enableIdentitySource(@PathVariable(value = "id") String id) { + boolean result = identitySourceService.enableIdentitySource(id); + //注册 + IdentitySourceEventUtils.register(id); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 禁用身份源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "禁用身份源") + @Audit(type = EventType.DISABLE_IDENTITY_RESOURCE) + @PutMapping(value = "/disable/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult disableIdentitySource(@PathVariable(value = "id") String id) { + boolean result = identitySourceService.disableIdentitySource(id); + //移除 + IdentitySourceEventUtils.destroy(id); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 删除身份源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "删除身份源") + @Audit(type = EventType.DELETE_IDENTITY_RESOURCE) + @DeleteMapping(value = "/delete/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult deleteIdentitySource(@PathVariable String id) { + boolean success = identitySourceService.deleteIdentitySource(id); + //移除 + IdentitySourceEventUtils.destroy(id); + return ApiRestResult. builder().result(success).build(); + } + + /** + * 身份源配置验证 + * + * @param param {@link IdentitySourceConfigValidatorParam} + * @return {@link Boolean} + */ + @Operation(summary = "身份源配置验证") + @PostMapping(value = "/config_validator") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult identitySourceConfigValidator(@RequestBody @Validated IdentitySourceConfigValidatorParam param) { + boolean success = identitySourceService.identitySourceConfigValidator(param); + return ApiRestResult. builder().result(success).build(); + } + + /** + * 身份源服务 + */ + private final IdentitySourceService identitySourceService; + + private final IdentitySourceConverter identitySourceConverter; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/identitysource/IdentitySourceEventController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/identitysource/IdentitySourceEventController.java new file mode 100644 index 00000000..54de9211 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/identitysource/IdentitySourceEventController.java @@ -0,0 +1,74 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.identitysource; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import cn.topiam.employee.console.pojo.query.identity.IdentitySourceEventRecordListQuery; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceEventRecordListResult; +import cn.topiam.employee.console.service.identitysource.IdentitySourceEventRecordService; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.AccountConstants.IDENTITY_SOURCE_PATH; + +/** + * 身份源事件记录 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/11 19:18 + */ +@Validated +@Tag(name = "身份源事件") +@RestController +@AllArgsConstructor +@RequestMapping(value = IDENTITY_SOURCE_PATH + + "/event", produces = MediaType.APPLICATION_JSON_VALUE) +public class IdentitySourceEventController { + + /** + * 身份源事件列表 + * + * @return {@link IdentitySourceEventRecordListResult} + */ + @Operation(summary = "身份源事件列表") + @GetMapping(value = "/record_list") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getIdentitySourceSynchronizeRecordList(PageModel pageModel, + IdentitySourceEventRecordListQuery query) { + Page results = identitySourceEventRecordService + .getIdentitySourceEventRecordList(query, pageModel); + return ApiRestResult.> builder().result(results) + .build(); + } + + /** + * 身份源同步Service + */ + private final IdentitySourceEventRecordService identitySourceEventRecordService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/identitysource/IdentitySourceSyncController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/identitysource/IdentitySourceSyncController.java new file mode 100644 index 00000000..9dd9fce6 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/identitysource/IdentitySourceSyncController.java @@ -0,0 +1,110 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.identitysource; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import cn.topiam.employee.audit.annotation.Audit; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.console.pojo.query.identity.IdentitySourceSyncHistoryListQuery; +import cn.topiam.employee.console.pojo.query.identity.IdentitySourceSyncRecordListQuery; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceSyncHistoryListResult; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceSyncRecordListResult; +import cn.topiam.employee.console.service.identitysource.IdentitySourceSyncService; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.AccountConstants.IDENTITY_SOURCE_PATH; + +/** + * 身份源同步记录 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/11 19:18 + */ +@Validated +@Tag(name = "身份源同步") +@RestController +@AllArgsConstructor +@RequestMapping(value = IDENTITY_SOURCE_PATH + "/sync", produces = MediaType.APPLICATION_JSON_VALUE) +public class IdentitySourceSyncController { + + /** + * 执行同步 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "执行身份源同步") + @Audit(type = EventType.IDENTITY_RESOURCE_SYNC) + @PostMapping(value = "/execute/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult executeIdentitySourceSync(@PathVariable String id) { + identitySourceSyncService.executeIdentitySourceSync(id); + return ApiRestResult.ok(); + } + + /** + * 同步历史列表 + * + * @return {@link IdentitySourceSyncHistoryListResult} + */ + @Operation(summary = "同步历史列表") + @GetMapping(value = "/history_list") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getIdentitySourceSyncHistoryList(PageModel pageModel, + IdentitySourceSyncHistoryListQuery query) { + Page results = identitySourceSyncService + .getIdentitySourceSyncHistoryList(query, pageModel); + return ApiRestResult.> builder().result(results) + .build(); + } + + /** + * 同步记录列表 + * + * @return {@link IdentitySourceSyncRecordListResult} + */ + @Operation(summary = "同步记录列表") + @GetMapping(value = "/record_list") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getIdentitySourceSyncRecordList(PageModel pageModel, + @Validated IdentitySourceSyncRecordListQuery query) { + Page results = identitySourceSyncService + .getIdentitySourceSyncRecordList(query, pageModel); + return ApiRestResult.> builder().result(results) + .build(); + } + + /** + * 身份源同步Service + */ + private final IdentitySourceSyncService identitySourceSyncService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/package-info.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/package-info.java new file mode 100644 index 00000000..2ec8dec7 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/package-info.java @@ -0,0 +1,22 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 22:35 + */ +package cn.topiam.employee.console.controller; \ No newline at end of file diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/session/SessionManageEndpoint.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/session/SessionManageEndpoint.java new file mode 100644 index 00000000..d8260b8c --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/session/SessionManageEndpoint.java @@ -0,0 +1,245 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.session; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.mapstruct.Mapper; +import org.springframework.security.core.session.SessionRegistry; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.annotation.JSONField; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import cn.topiam.employee.audit.annotation.Audit; +import cn.topiam.employee.audit.context.AuditContext; +import cn.topiam.employee.audit.entity.Target; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.audit.enums.TargetType; +import cn.topiam.employee.common.enums.UserType; +import cn.topiam.employee.common.geo.GeoLocation; +import cn.topiam.employee.core.security.session.SessionDetails; +import cn.topiam.employee.core.security.session.TopIamSessionBackedSessionRegistry; +import cn.topiam.employee.core.security.util.SecurityUtils; +import cn.topiam.employee.support.context.ApplicationContextHelp; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.result.ApiRestResult; +import cn.topiam.employee.support.util.HttpResponseUtils; +import cn.topiam.employee.support.web.useragent.UserAgent; + +import lombok.Data; +import lombok.experimental.Accessors; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.USERNAME; + +import static cn.topiam.employee.common.constants.SessionConstants.SESSION_PATH; +import static cn.topiam.employee.support.constant.EiamConstants.DEFAULT_DATE_TIME_FORMATTER_PATTERN; + +/** + * 会话管理 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/8 21:39 + */ +@Tag(name = "会话管理") +@RestController +@RequestMapping(value = SESSION_PATH) +public class SessionManageEndpoint { + /** + * list + * + * @param req {@link HttpServletRequest} + * @param resp {@link HttpServletResponse} + * @return {@link ApiRestResult} + */ + @Operation(summary = "在线会话") + @GetMapping("/list") + public ApiRestResult> list(HttpServletRequest req, + HttpServletResponse resp) { + List list = new ArrayList<>(); + List principals = new ArrayList<>(); + SessionRegistry registry = ApplicationContextHelp.getBean(SessionRegistry.class); + if (registry instanceof TopIamSessionBackedSessionRegistry) { + //普通用户只能看自己的会话 + if (SecurityUtils.getCurrentUser().getUserType().equals(UserType.USER)) { + principals = ((TopIamSessionBackedSessionRegistry) (registry)) + .getPrincipals(SecurityUtils.getCurrentUser().getUsername()); + } + //管理员看所有 + if (SecurityUtils.getCurrentUser().getUserType().equals(UserType.ADMIN)) { + //根据用户查询 + if (StringUtils.isNoneBlank(req.getParameter(USERNAME))) { + principals = ((TopIamSessionBackedSessionRegistry) (registry)) + .getPrincipals(req.getParameter(USERNAME)); + } else { + principals = registry.getAllPrincipals(); + } + } + //封装数据 + principals.forEach(principal -> { + if (principal instanceof SessionDetails) { + //过滤掉当前用户的会话 + if (!((SessionDetails) principal).getSessionId() + .equals(req.getSession().getId())) { + //@formatter:off + OnlineUserConverter userConverter = ApplicationContextHelp.getBean(OnlineUserConverter.class); + OnlineSession user = userConverter.sessionDetailsToOnlineSession(((SessionDetails) principal)); + list.add(user); + //@formatter:on + } + } + }); + + } + // 封装返回 + return ApiRestResult.> builder().result(list).build(); + } + + /** + * remove + * + * @param req {@link HttpServletRequest} + * @param resp {@link HttpServletResponse} + * @return {@link ApiRestResult} + */ + @Lock + @Preview + @Operation(summary = "下线会话") + @Audit(type = EventType.DOWN_LINE_SESSION) + @DeleteMapping("/remove") + public ApiRestResult remove(HttpServletRequest req, HttpServletResponse resp) { + String sessionIds = req.getParameter("sessionIds"); + //session id blank + if (!StringUtils.isNoneBlank(sessionIds)) { + HttpResponseUtils.flushResponse(resp, JSON.toJSONString(ApiRestResult.err("会话ID不存在!"))); + } + SessionRegistry registry = ApplicationContextHelp.getBean(SessionRegistry.class); + String[] ids = sessionIds.split(","); + Arrays.stream(ids).forEach((i) -> { + //如果sessionId等于当前操作用户sessionId不操作 + if (!req.getSession().getId().equals(i)) { + AuditContext + .setTarget(Target.builder().id(i.toString()).type(TargetType.SESSION).build()); + registry.removeSessionInformation(i); + } + }); + //返回 + return ApiRestResult.ok(); + } + +} + +/** + * 在线用户 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/9/8 21:42 + */ +@Data +@Accessors(chain = true) +class OnlineSession implements Serializable { + + @Serial + private static final long serialVersionUID = 8227098865368453321L; + /** + * 用户ID + */ + private String id; + /** + * 用户名 + */ + private String username; + + /** + * 活动地点 + */ + @JsonTypeInfo(use = JsonTypeInfo.Id.NONE) + private GeoLocation geoLocation; + + /** + * 用户代理 + */ + @JsonTypeInfo(use = JsonTypeInfo.Id.NONE) + private UserAgent userAgent; + + /** + * 登录时间 + */ + @JSONField(format = DEFAULT_DATE_TIME_FORMATTER_PATTERN) + private LocalDateTime loginTime; + + /** + * 最后请求时间 + */ + @JSONField(format = DEFAULT_DATE_TIME_FORMATTER_PATTERN) + private LocalDateTime lastRequest; + + /** + * session ID + */ + private String sessionId; + +} + +@Mapper(componentModel = "spring") +interface OnlineUserConverter { + /** + * 系统用户转在线会话 + * + * @param sessionDetails {@link SessionDetails} + * @return {@link OnlineSession} + */ + default OnlineSession sessionDetailsToOnlineSession(SessionDetails sessionDetails) { + if (sessionDetails == null) { + return null; + } + + OnlineSession onlineSession = new OnlineSession(); + //ID + onlineSession.setId(sessionDetails.getId()); + //用户名 + onlineSession.setUsername(sessionDetails.getUsername()); + //session id + onlineSession.setSessionId(sessionDetails.getSessionId()); + //地理位置 + onlineSession.setGeoLocation(sessionDetails.getGeoLocation()); + //用户代理 + onlineSession.setUserAgent(sessionDetails.getUserAgent()); + //登录时间 + onlineSession.setLoginTime(sessionDetails.getLoginTime()); + //最后请求时间 + onlineSession.setLastRequest(sessionDetails.getLastRequestTime()); + return onlineSession; + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/AdministratorController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/AdministratorController.java new file mode 100644 index 00000000..2aa2f515 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/AdministratorController.java @@ -0,0 +1,221 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.setting; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import cn.topiam.employee.audit.annotation.Audit; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.common.enums.CheckValidityType; +import cn.topiam.employee.common.enums.UserStatus; +import cn.topiam.employee.console.pojo.query.setting.AdministratorListQuery; +import cn.topiam.employee.console.pojo.result.app.AppPermissionRoleListResult; +import cn.topiam.employee.console.pojo.result.setting.AdministratorListResult; +import cn.topiam.employee.console.pojo.result.setting.AdministratorResult; +import cn.topiam.employee.console.pojo.save.setting.AdministratorCreateParam; +import cn.topiam.employee.console.pojo.update.setting.AdministratorUpdateParam; +import cn.topiam.employee.console.service.setting.AdministratorService; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.SettingConstants.SETTING_PATH; + +/** + * 管理员 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/13 22:09 + */ +@Validated +@Tag(name = "平台管理员") +@RestController +@AllArgsConstructor +@RequestMapping(value = SETTING_PATH + + "/administrator", produces = MediaType.APPLICATION_JSON_VALUE) +public class AdministratorController { + /** + * 获取管理员列表 + * + * @param model {@link PageModel} + * @return {@link AppPermissionRoleListResult} + */ + @Operation(summary = "获取管理员列表") + @GetMapping(value = "/list") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getAdministratorList(PageModel model, + AdministratorListQuery query) { + Page result = administratorService.getAdministratorList(model, + query); + return ApiRestResult.> builder().result(result).build(); + } + + /** + * 创建管理员 + * + * @param param {@link AdministratorCreateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "创建管理员") + @Audit(type = EventType.ADD_ADMINISTRATOR) + @PostMapping(value = "/create") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult createAdministrator(@RequestBody @Validated AdministratorCreateParam param) { + Boolean result = administratorService.createAdministrator(param); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 修改管理员 + * + * @param param {@link AdministratorUpdateParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "修改管理员") + @Audit(type = EventType.UPDATE_ADMINISTRATOR) + @PutMapping(value = "/update/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult updateAdministrator(@PathVariable(value = "id") String id, + @RequestBody @Validated AdministratorUpdateParam param) { + Boolean result = administratorService.updateAdministrator(param); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 删除管理员 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "删除管理员") + @DeleteMapping(value = "/delete/{id}") + @Audit(type = EventType.DELETE_ADMINISTRATOR) + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult deleteAdministrator(@PathVariable(value = "id") String id) { + Boolean result = administratorService.deleteAdministrator(id); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 启用管理员 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "启用管理员") + @Audit(type = EventType.ENABLE_ADMINISTRATOR) + @PutMapping(value = "/enable/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult enableAdministrator(@PathVariable(value = "id") String id) { + Boolean result = administratorService.updateAdministratorStatus(id, UserStatus.ENABLE); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 禁用管理员 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "禁用管理员") + @Audit(type = EventType.DISABLE_ADMINISTRATOR) + @PutMapping(value = "/disable/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult disableAdministrator(@PathVariable(value = "id") String id) { + Boolean result = administratorService.updateAdministratorStatus(id, UserStatus.DISABLE); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 重置管理员密码 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Validated + @Operation(summary = "重置管理员密码") + @Audit(type = EventType.RESET_ADMINISTRATOR_PASSWORD) + @PutMapping(value = "/reset_password") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult resetAdministratorPassword(@NotEmpty(message = "ID不能为空") @Parameter(description = "ID") String id, + @NotEmpty(message = "密码不能为空") @Parameter(description = "密码") String password) { + Boolean result = administratorService.resetAdministratorPassword(id, password); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 根据ID获取管理员 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Operation(summary = "获取管理员信息") + @GetMapping(value = "/get/{id}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getAdministrator(@PathVariable(value = "id") String id) { + AdministratorResult result = administratorService.getAdministrator(id); + //返回 + return ApiRestResult. builder().result(result).build(); + } + + /** + * 参数有效性验证 + * + * @return {@link Boolean} + */ + @Operation(summary = "参数有效性验证") + @GetMapping(value = "/param_check") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult administratorParamCheck(@Parameter(description = "验证类型") @NotNull(message = "验证类型不能为空") CheckValidityType type, + @Parameter(description = "值") @NotEmpty(message = "验证值不能为空") String value, + @Parameter(description = "ID") Long id) { + Boolean result = administratorService.administratorParamCheck(type, value, id); + //返回 + return ApiRestResult. builder().result(result).build(); + } + + /** + * AdministratorService + */ + private final AdministratorService administratorService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/GeoIpLibraryController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/GeoIpLibraryController.java new file mode 100644 index 00000000..7a85223c --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/GeoIpLibraryController.java @@ -0,0 +1,121 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.setting; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import cn.topiam.employee.audit.annotation.Audit; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.common.geo.GeoLocation; +import cn.topiam.employee.console.pojo.result.setting.GeoIpProviderResult; +import cn.topiam.employee.console.pojo.save.setting.GeoIpProviderSaveParam; +import cn.topiam.employee.console.service.setting.GeoLocationSettingService; +import cn.topiam.employee.support.context.ApplicationContextHelp; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.ConfigBeanNameConstants.GEO_LOCATION; +import static cn.topiam.employee.common.constants.SettingConstants.SETTING_PATH; +import static cn.topiam.employee.core.setting.constant.GeoIpProviderConstants.IPADDRESS_SETTING_NAME; + +/** + * IP地址库 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/13 22:09 + */ +@Validated +@Tag(name = "IP地址库") +@RestController +@AllArgsConstructor +@RequestMapping(value = SETTING_PATH + "/geo_ip", produces = MediaType.APPLICATION_JSON_VALUE) +public class GeoIpLibraryController { + /** + * 获取配置 + * + * @return {@link GeoIpProviderResult} + */ + @Operation(summary = "获取地理位置配置") + @GetMapping("config") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getGeoIpLibrary() { + return ApiRestResult. builder() + .result(geoLocationSettingService.getGeoIpLibrary()).build(); + } + + /** + * 保存配置 + * + * @param param {@link GeoIpProviderSaveParam} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "保存地理位置配置") + @Audit(type = EventType.SAVE_GEO_LOCATION_SERVICE) + @PostMapping("save") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult saveGeoIpLibrary(@RequestBody @Validated GeoIpProviderSaveParam param) { + return ApiRestResult. builder() + .result(geoLocationSettingService.saveGeoIpLibrary(param)).build(); + } + + /** + * 禁用地理位置服务 + * + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "禁用地理位置服务") + @Audit(type = EventType.OFF_GEO_LOCATION_SERVICE) + @PutMapping("/disable") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult disableGeoIpLibrary() { + Boolean setting = geoLocationSettingService.removeSetting(IPADDRESS_SETTING_NAME); + // refresh + ApplicationContextHelp.refresh(GEO_LOCATION); + return ApiRestResult. builder().result(setting).build(); + } + + /** + * 查询IP地址位置信息 + * + * @param ip {@link String} + * @return {@link ApiRestResult} + */ + @Operation(summary = "查询IP地址位置信息") + @GetMapping(value = "/get_location") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getGeoLocation(@Parameter(description = "IP") String ip) { + return ApiRestResult. builder() + .result(geoLocationSettingService.getGeoLocation(ip)).build(); + } + + private GeoLocationSettingService geoLocationSettingService; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/MailProviderController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/MailProviderController.java new file mode 100644 index 00000000..c39ba66c --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/MailProviderController.java @@ -0,0 +1,140 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.setting; + +import java.util.HashMap; + +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import cn.topiam.employee.audit.annotation.Audit; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.common.enums.MailType; +import cn.topiam.employee.console.pojo.result.setting.EmailProviderConfigResult; +import cn.topiam.employee.console.pojo.save.authentication.InitializeAdminSaveParam; +import cn.topiam.employee.console.pojo.save.setting.MailProviderSaveParam; +import cn.topiam.employee.console.service.setting.MessageSettingService; +import cn.topiam.employee.core.context.ServerContextHelp; +import cn.topiam.employee.core.message.MsgVariable; +import cn.topiam.employee.core.message.mail.MailMsgEventPublish; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.SettingConstants.SETTING_PATH; + +/** + * 消息设置 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/24 22:36 + */ +@Validated +@Tag(name = "邮件提供商") +@RestController +@AllArgsConstructor +@RequestMapping(value = SETTING_PATH + "/message/mail_provider") +public class MailProviderController { + + /** + * 保存邮件服务商配置 + * + * @param param {@link InitializeAdminSaveParam} + * @return {@link ApiRestResult} + */ + @Lock + @Preview + @Validated + @Operation(summary = "保存邮件服务提供商配置") + @Audit(type = EventType.SAVE_MAIL_SERVICE) + @PostMapping("save") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult saveEmailProviderConfig(@RequestBody MailProviderSaveParam param) { + Boolean result = messageSettingService.saveMailProviderConfig(param); + return ApiRestResult.ok(result); + } + + /** + * 获取邮件服务商配置 + * + * @return {@link ApiRestResult} + */ + @Operation(summary = "获取邮件服务提供商配置") + @GetMapping("config") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getEmailProviderConfig() { + return ApiRestResult.ok(messageSettingService.getMailProviderConfig()); + } + + /** + * 禁用邮件提供商 + * + * @return {@link ApiRestResult} + */ + @Lock + @Preview + @Validated + @Operation(summary = "禁用邮件服务提供商") + @Audit(type = EventType.OFF_MAIL_SERVICE) + @PutMapping(value = "/disable") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult disableMailProvider() { + Boolean result = messageSettingService.disableMailProvider(); + return ApiRestResult.ok(result); + } + + /** + * 邮件发送测试 + * + * @param mailType {@link MailType} + * @param receiver {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "发送测试邮件") + @GetMapping(value = "/test") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult sendMail(MailType mailType, String receiver) { + HashMap map = new HashMap<>(16); + if (mailType == MailType.UPDATE_PASSWORD || mailType == MailType.UPDATE_BIND_MAIL + || mailType == MailType.VERIFY_EMAIL || mailType == MailType.RESET_PASSWORD) { + map.put(MsgVariable.VERIFY_CODE, RandomStringUtils.randomAlphanumeric(6)); + } + map.put(MsgVariable.TEST, "(TEST)"); + map.put(MsgVariable.EXPIRE_DAYS, "3"); + map.put("verify_link", ServerContextHelp.getPortalPublicBaseUrl()); + mailMsgEventPublish.publish(mailType, receiver, map); + return ApiRestResult.ok(); + } + + /** + * MailMsgEventPublish + */ + private final MailMsgEventPublish mailMsgEventPublish; + /** + * MessageSettingService + */ + private final MessageSettingService messageSettingService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/MailTemplateController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/MailTemplateController.java new file mode 100644 index 00000000..f41574c5 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/MailTemplateController.java @@ -0,0 +1,127 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.setting; + +import java.util.List; +import java.util.Objects; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import cn.topiam.employee.audit.annotation.Audit; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.common.enums.MailType; +import cn.topiam.employee.console.pojo.result.setting.EmailTemplateListResult; +import cn.topiam.employee.console.pojo.result.setting.EmailTemplateResult; +import cn.topiam.employee.console.pojo.save.setting.EmailCustomTemplateSaveParam; +import cn.topiam.employee.console.service.setting.MailTemplateService; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.SettingConstants.SETTING_PATH; + +/** + * 邮件配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/24 22:36 + */ +@Validated +@Tag(name = "邮件模板") +@RestController +@AllArgsConstructor +@RequestMapping(value = SETTING_PATH + + "/mail_template", produces = MediaType.APPLICATION_JSON_VALUE) +public class MailTemplateController { + + /** + * 获取邮件模板列表 + * + * @return {@link EmailTemplateResult} + */ + @Validated + @GetMapping(value = "/list") + @Operation(summary = "获取邮件模板列表") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getEmailTemplateList() { + List list = templateService.getEmailTemplateList(); + return ApiRestResult.> builder().result(list).build(); + } + + /** + * 根据模板类型查询邮件模板 + * + * @param type {@link String} + * @return {@link EmailTemplateResult} + */ + @Operation(summary = "获取邮件模板信息") + @GetMapping(value = "/{type}") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getEmailTemplate(@PathVariable(value = "type") String type) { + MailType templateType = MailType.getType(type); + EmailTemplateResult result = templateService.getEmailTemplate(templateType); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 保存邮件模板 + * + * @param type {@link String} + * @return {@link EmailTemplateResult} + */ + @Lock + @Preview + @PutMapping(value = "/save_custom/{type}") + @Audit(type = EventType.SAVE_GEO_LOCATION_SERVICE) + @Operation(summary = "保存邮件模板") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult saveCustomEmailTemplate(@PathVariable(value = "type") String type, + @RequestBody @Validated EmailCustomTemplateSaveParam param) { + MailType templateType = MailType.getType(type); + return ApiRestResult. builder() + .result(!Objects.isNull(templateService.saveCustomEmailTemplate(templateType, param))) + .build(); + } + + /** + * 关闭自定义邮件模板 + * + * @return {@link EmailTemplateResult} + */ + @Lock + @Preview + @PutMapping(value = "/disable_custom/{type}") + @Operation(summary = "禁用自定义邮件模板") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult disableCustomEmailTemplate(@PathVariable(value = "type") String type) { + templateService.disableCustomEmailTemplate(MailType.getType(type)); + return ApiRestResult.ok(); + } + + /** + * 邮件模板服务类 + */ + private final MailTemplateService templateService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/SecurityController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/SecurityController.java new file mode 100644 index 00000000..cba1e100 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/SecurityController.java @@ -0,0 +1,214 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.setting; + +import java.util.List; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import cn.topiam.employee.audit.annotation.Audit; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.console.pojo.result.setting.*; +import cn.topiam.employee.console.pojo.save.setting.PasswordPolicySaveParam; +import cn.topiam.employee.console.pojo.save.setting.SecurityBasicSaveParam; +import cn.topiam.employee.console.pojo.save.setting.SecurityCaptchaSaveParam; +import cn.topiam.employee.console.pojo.save.setting.SecurityMfaSaveParam; +import cn.topiam.employee.console.service.setting.PasswordPolicyService; +import cn.topiam.employee.console.service.setting.SecuritySettingService; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.SettingConstants.SETTING_PATH; + +/** + * 安全设置 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/10/4 20:37 + */ +@Validated +@Tag(name = "安全设置") +@RestController +@RequestMapping(value = SETTING_PATH + "/security", produces = MediaType.APPLICATION_JSON_VALUE) +@AllArgsConstructor +public class SecurityController { + + /** + * 获取高级配置 + * + * @return {@link SecurityBasicConfigResult} + */ + @Operation(summary = "获取基础配置") + @GetMapping(value = "/basic/config") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getBasicConfig() { + SecurityBasicConfigResult result = securitySettingService.getBasicConfig(); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 保存高级配置 + * + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "保存基础配置") + @Audit(type = EventType.SAVE_LOGIN_SECURITY_BASIC_SETTINGS) + @PostMapping(value = "/basic/save") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult save(@Validated @RequestBody SecurityBasicSaveParam param) { + return ApiRestResult. builder() + .result(securitySettingService.saveBasicConfig(param)).build(); + } + + /** + * 密码策略配置 + * + * @return {@link List} + */ + @GetMapping(value = "/password_policy/config") + @Operation(summary = "获取密码策略") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult config() { + PasswordPolicyConfigResult result = passwordPolicyService.getPasswordPolicyConfig(); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 保存密码策略 + * + * @return {@link List} + */ + @Lock + @Preview + @PostMapping(value = "/password_policy/save") + @Audit(type = EventType.SAVE_PASSWORD_POLICY_SETTINGS) + @Operation(summary = "保存密码策略") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult save(@Validated @RequestBody PasswordPolicySaveParam param) { + return ApiRestResult. builder() + .result(passwordPolicyService.savePasswordPolicyConfig(param)).build(); + } + + /** + * 系统弱密码库 + * + * @return {@link List} + */ + @GetMapping(value = "/weak_password_lib/list") + @Operation(summary = "获取系统弱密码库") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getWeakPasswordLibList() { + List result = passwordPolicyService.getWeakPasswordLibList(); + return ApiRestResult.> builder().result(result).build(); + } + + /** + * 获取MFA配置 + * + * @return {@link SecurityMfaConfigResult} + */ + @Operation(summary = "获取MFA配置") + @GetMapping(value = "/mfa/config") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getMfaConfig() { + SecurityMfaConfigResult result = securitySettingService.getMfaConfig(); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 保存MFA配置 + * + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "保存MFA配置") + @Audit(type = EventType.SAVE_MFA_SETTINGS) + @PostMapping(value = "/mfa/save") + @Validated + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult saveMfaConfig(@RequestBody SecurityMfaSaveParam param) { + return ApiRestResult. builder().result(securitySettingService.saveMfaConfig(param)) + .build(); + } + + /** + * 获取行为验证码配置 + * + * @return {@link SecurityCaptchaConfigResult} + */ + @Operation(summary = "获取行为验证码配置") + @GetMapping(value = "/captcha/config") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getCaptchaProviderConfig() { + SecurityCaptchaConfigResult result = securitySettingService.getCaptchaProviderConfig(); + return ApiRestResult. builder().result(result).build(); + } + + /** + * 保存行为验证码配置 + * + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "保存行为验证码配置") + @Audit(type = EventType.SAVE_CAPTCHA_PROVIDER) + @PostMapping(value = "/captcha/save") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult saveCaptchaProviderConfig(@Validated @RequestBody SecurityCaptchaSaveParam param) { + return ApiRestResult. builder() + .result(securitySettingService.saveCaptchaProviderConfig(param)).build(); + } + + /** + * 禁用行为验证码 + * + * @return {@link ApiRestResult} + */ + @Lock + @Preview + @Validated + @Operation(summary = "禁用行为验证码") + @Audit(type = EventType.OFF_MAIL_SERVICE) + @PutMapping(value = "/captcha/disable") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult disableCaptchaProvider() { + Boolean result = securitySettingService.disableCaptchaProvider(); + return ApiRestResult.ok(result); + } + + /** + * 密码策略实现类 + */ + private final PasswordPolicyService passwordPolicyService; + /** + * SecuritySettingService + */ + private final SecuritySettingService securitySettingService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/SmsProviderController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/SmsProviderController.java new file mode 100644 index 00000000..99447ed0 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/SmsProviderController.java @@ -0,0 +1,139 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.setting; + +import java.util.LinkedHashMap; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import cn.topiam.employee.audit.annotation.Audit; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.common.enums.MailType; +import cn.topiam.employee.common.enums.SmsType; +import cn.topiam.employee.console.pojo.save.setting.SmsProviderSaveParam; +import cn.topiam.employee.console.pojo.setting.SmsProviderConfigResult; +import cn.topiam.employee.console.service.setting.MessageSettingService; +import cn.topiam.employee.core.message.sms.SmsMsgEventPublish; +import cn.topiam.employee.core.security.password.PasswordGenerator; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.SettingConstants.SETTING_PATH; + +/** + * 消息设置 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/24 22:36 + */ +@Validated +@Tag(name = "短信提供商") +@RestController +@AllArgsConstructor +@RequestMapping(value = SETTING_PATH + "/message/sms_provider") +public class SmsProviderController { + /** + * 禁用短信提供商 + * + * @return {@link ApiRestResult} + */ + @Lock + @Preview + @Validated + @Operation(summary = "禁用短信提供商") + @Audit(type = EventType.OFF_SMS_SERVICE) + @PutMapping(value = "/disable") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult disableSmsProvider() { + Boolean result = messageSettingService.disableSmsProvider(); + return ApiRestResult.ok(result); + } + + /** + * 保存短信提供商配置 + * + * @param param {@link SmsProviderSaveParam} + * @return {@link ApiRestResult} + */ + @Lock + @Preview + @Validated + @Operation(summary = "保存短信提供商配置") + @Audit(type = EventType.SAVE_SMS_SERVICE) + @PostMapping("save") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult saveSmsProviderConfig(@RequestBody SmsProviderSaveParam param) { + Boolean result = messageSettingService.saveSmsProviderConfig(param); + return ApiRestResult.ok(result); + } + + /** + * 获取短信提供商配置 + * + * @return {@link ApiRestResult} + */ + @Operation(summary = "获取短信提供商配置") + @GetMapping("/config") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getSmsProviderConfig() { + SmsProviderConfigResult result = messageSettingService.getSmsProviderConfig(); + return ApiRestResult.ok(result); + } + + /** + * 短信发送测试 + * + * @param smsType {@link MailType} + * @param receiver {@link String} + * @return {@link Boolean} + */ + @Lock + @Preview + @Operation(summary = "发送测试短信") + @GetMapping(value = "/test") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult sendSms(SmsType smsType, String receiver) { + if (SmsType.WELCOME_SMS == smsType || SmsType.RESET_PASSWORD == smsType + || SmsType.WARING == smsType) { + LinkedHashMap parameter = new LinkedHashMap<>(16); + parameter.put("username", "test"); + parameter.put("password", passwordGenerator.generatePassword()); + parameter.put("expire_days", "3"); + smsMsgEventPublish.publish(smsType, receiver, parameter); + } else { + smsMsgEventPublish.publishVerifyCode(receiver, smsType, "123456"); + } + return ApiRestResult.ok(); + } + + /** + * MessageSettingService + */ + private final MessageSettingService messageSettingService; + + private final SmsMsgEventPublish smsMsgEventPublish; + + private final PasswordGenerator passwordGenerator; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/SmsTemplateController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/SmsTemplateController.java new file mode 100644 index 00000000..7448d65c --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/SmsTemplateController.java @@ -0,0 +1,74 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.setting; + +import java.util.List; + +import javax.validation.constraints.NotNull; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import cn.topiam.employee.common.enums.Language; +import cn.topiam.employee.console.pojo.result.setting.SmsTemplateListResult; +import cn.topiam.employee.console.service.setting.SmsTemplateService; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.SettingConstants.SETTING_PATH; + +/** + * 短信模版配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/7/24 22:36 + */ +@Validated +@Tag(name = "短信模板") +@RestController +@AllArgsConstructor +@RequestMapping(value = SETTING_PATH + "/sms_template", produces = MediaType.APPLICATION_JSON_VALUE) +public class SmsTemplateController { + + /** + * 获取短信模板列表 + * + * @param language {@link Language} + * @return {@link SmsTemplateListResult} + */ + @GetMapping(value = "/list") + @Operation(summary = "获取短信模板列表") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult> getSmsTemplateList(@NotNull(message = "语言类型不能为空") @Parameter(description = "语言") Language language) { + List list = smsTemplateService.getSmsTemplateList(language); + return ApiRestResult.> builder().result(list).build(); + } + + /** + * 短信模板服务类 + */ + private final SmsTemplateService smsTemplateService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/StorageController.java b/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/StorageController.java new file mode 100644 index 00000000..688ca1bc --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/controller/setting/StorageController.java @@ -0,0 +1,105 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.controller.setting; + +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import cn.topiam.employee.audit.annotation.Audit; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.console.pojo.result.setting.StorageProviderConfigResult; +import cn.topiam.employee.console.pojo.save.setting.StorageConfigSaveParam; +import cn.topiam.employee.console.service.setting.StorageSettingService; +import cn.topiam.employee.support.lock.Lock; +import cn.topiam.employee.support.preview.Preview; +import cn.topiam.employee.support.result.ApiRestResult; + +import lombok.AllArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import static cn.topiam.employee.common.constants.SettingConstants.SETTING_PATH; + +/** + * 存储配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/10/30 21:16 + */ +@Validated +@Tag(name = "存储配置") +@RestController +@RequestMapping(value = SETTING_PATH + "/storage", produces = MediaType.APPLICATION_JSON_VALUE) +@AllArgsConstructor +public class StorageController { + + /** + * 启用/禁用存储配置服务 + * + * @return {@link ApiRestResult} + */ + @Lock + @Preview + @Validated + @Operation(summary = "禁用存储服务") + @Audit(type = EventType.OFF_STORAGE_SERVICE) + @PutMapping(value = "/disable") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult disableStorage() { + Boolean result = storageSettingsService.disableStorage(); + return ApiRestResult.ok(result); + } + + /** + * 保存存储配置 + * + * @param param {@link StorageConfigSaveParam} + * @return {@link ApiRestResult} + */ + @Lock + @Preview + @Validated + @Operation(summary = "保存存储服务配置") + @Audit(type = EventType.SAVE_STORAGE_SERVICE) + @PostMapping(value = "/save") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult saveStorageConfig(@RequestBody StorageConfigSaveParam param) { + Boolean result = storageSettingsService.saveStorageConfig(param); + return ApiRestResult.ok(result); + } + + /** + * 获取存储配置 + * + * @return {@link ApiRestResult} + */ + @Operation(summary = "获取存储服务配置") + @GetMapping(value = "/config") + @PreAuthorize(value = "authenticated and hasAuthority(T(cn.topiam.employee.core.security.authorization.Roles).ADMIN)") + public ApiRestResult getStorageConfig() { + StorageProviderConfigResult result = storageSettingsService.getStorageConfig(); + return ApiRestResult.ok(result); + } + + /** + * StorageSettingsService + */ + private final StorageSettingService storageSettingsService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/account/OrganizationConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/account/OrganizationConverter.java new file mode 100644 index 00000000..e7386ad3 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/account/OrganizationConverter.java @@ -0,0 +1,166 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.account; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.util.CollectionUtils; + +import cn.topiam.employee.common.entity.account.OrganizationEntity; +import cn.topiam.employee.console.pojo.result.account.OrganizationChildResult; +import cn.topiam.employee.console.pojo.result.account.OrganizationResult; +import cn.topiam.employee.console.pojo.result.account.OrganizationRootResult; +import cn.topiam.employee.console.pojo.result.account.OrganizationTreeResult; +import cn.topiam.employee.console.pojo.save.account.OrganizationCreateParam; +import cn.topiam.employee.console.pojo.update.account.OrganizationUpdateParam; + +/** + * 组织架构数据映射 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/14 21:25 + */ +@Mapper(componentModel = "spring") +public interface OrganizationConverter { + + /** + * 组织实体转换为List结果 + * + * @param data {@link List} + * @return {@link List} + */ + default List entityConvertToChildOrgListResult(List data) { + List list = new ArrayList<>(); + if (!CollectionUtils.isEmpty(data)) { + for (OrganizationEntity entity : data) { + list.add(entityConvertToChildOrgListResult(entity)); + } + } + return list; + } + + /** + * 组织实体转换为TreeList结果 + * + * @param data {@link List} + * @param parentId {@link String} + * @return {@link List} + */ + default List entityConvertToChildOrgTreeListResult(String parentId, + List data) { + List list = new ArrayList<>(); + List entityList = data.stream() + .filter(one -> Objects.equals(parentId, one.getParentId())).toList(); + for (OrganizationEntity organizationEntity : entityList) { + OrganizationTreeResult result = entityConvertToChildOrgTreeListResult( + organizationEntity); + result.setChildren( + entityConvertToChildOrgTreeListResult(organizationEntity.getId(), data)); + list.add(result); + } + return list; + } + + /** + * 组织实体转换为组织分页结果 + * + * @param data {@link OrganizationEntity} + * @return {@link OrganizationResult} + */ + @Mapping(target = "type", source = "type.desc") + @Mapping(target = "dataOrigin", source = "dataOrigin.desc") + OrganizationChildResult entityConvertToChildOrgListResult(OrganizationEntity data); + + /** + * 组织实体转换为组织分页结果 + * + * @param data {@link OrganizationEntity} + * @return {@link OrganizationResult} + */ + @Mapping(target = "children", ignore = true) + @Mapping(target = "type", source = "type.desc") + @Mapping(target = "dataOrigin", source = "dataOrigin.desc") + OrganizationTreeResult entityConvertToChildOrgTreeListResult(OrganizationEntity data); + + /** + * 组织实体转换为组织分页结果 + * + * @param data {@link OrganizationEntity} + * @return {@link OrganizationResult} + */ + @Mapping(target = "type", source = "type.desc") + @Mapping(target = "dataOrigin", source = "dataOrigin.desc") + OrganizationRootResult entityConvertToRootOrgListResult(OrganizationEntity data); + + /** + * 组织创建参数转换为组织实体 + * + * @param param {@link OrganizationCreateParam} + * @return {@link OrganizationEntity} + */ + @Mapping(target = "identitySourceId", ignore = true) + @Mapping(target = "path", ignore = true) + @Mapping(target = "displayPath", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "dataOrigin", expression = "java(cn.topiam.employee.common.enums.DataOrigin.INPUT)") + @Mapping(target = "remark", ignore = true) + @Mapping(target = "leaf", expression = "java(Boolean.TRUE)") + @Mapping(target = "enabled", expression = "java(Boolean.TRUE)") + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + @Mapping(target = "order", source = "order", defaultValue = "9999L") + @Mapping(target = "parentId", source = "parentId", defaultExpression = "java(cn.topiam.employee.support.constant.EiamConstants.ROOT_NODE)") + @Mapping(target = "externalId", source = "externalId", defaultExpression = "java(new org.springframework.util.JdkIdGenerator().generateId().toString())") + OrganizationEntity orgCreateParamConvertToEntity(OrganizationCreateParam param); + + /** + * 组织修改参数转换为组织实体 + * + * @param param {@link OrganizationUpdateParam} + * @return {@link OrganizationEntity} + */ + @Mapping(target = "identitySourceId", ignore = true) + @Mapping(target = "path", ignore = true) + @Mapping(target = "displayPath", ignore = true) + @Mapping(target = "dataOrigin", ignore = true) + @Mapping(target = "parentId", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "leaf", ignore = true) + @Mapping(target = "code", ignore = true) + @Mapping(target = "remark", ignore = true) + @Mapping(target = "externalId", ignore = true) + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + OrganizationEntity orgUpdateParamConvertToEntity(OrganizationUpdateParam param); + + /** + * 实体转组织详情结果 + * + * @param organization {@link OrganizationEntity} + * @return {@link OrganizationResult} + */ + OrganizationResult entityConvertToOrgDetailResult(OrganizationEntity organization); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/account/UserConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/account/UserConverter.java new file mode 100644 index 00000000..d1f5b559 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/account/UserConverter.java @@ -0,0 +1,344 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.account; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.search.sort.FieldSortBuilder; +import org.elasticsearch.search.sort.SortBuilder; +import org.elasticsearch.search.sort.SortBuilders; +import org.elasticsearch.search.sort.SortOrder; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +import cn.topiam.employee.audit.entity.AuditElasticSearchEntity; +import cn.topiam.employee.audit.entity.Event; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.audit.event.type.AppEventType; +import cn.topiam.employee.audit.event.type.AuthenticationEventType; +import cn.topiam.employee.common.entity.account.UserDetailEntity; +import cn.topiam.employee.common.entity.account.UserEntity; +import cn.topiam.employee.common.entity.account.po.UserPO; +import cn.topiam.employee.common.entity.app.AppEntity; +import cn.topiam.employee.common.repository.app.AppRepository; +import cn.topiam.employee.console.pojo.result.account.UserListResult; +import cn.topiam.employee.console.pojo.result.account.UserLoginAuditListResult; +import cn.topiam.employee.console.pojo.result.account.UserResult; +import cn.topiam.employee.console.pojo.save.account.UserCreateParam; +import cn.topiam.employee.console.pojo.update.account.UserUpdateParam; +import cn.topiam.employee.support.context.ApplicationContextHelp; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import static cn.topiam.employee.audit.entity.Actor.ACTOR_ID; +import static cn.topiam.employee.audit.entity.Event.EVENT_TIME; +import static cn.topiam.employee.audit.entity.Event.EVENT_TYPE; +import static cn.topiam.employee.audit.enums.EventType.LOGIN_PORTAL; +import static cn.topiam.employee.audit.enums.TargetType.PORTAL; +import static cn.topiam.employee.audit.service.converter.AuditDataConverter.SORT_EVENT_TIME; +import static cn.topiam.employee.support.util.PhoneUtils.getPhoneAreaCode; +import static cn.topiam.employee.support.util.PhoneUtils.getPhoneNumber; + +/** + * 用户映射 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/14 20:45 + */ +@Mapper(componentModel = "spring") +public interface UserConverter { + + /** + * 用户实体转换为用户分页结果 + * + * @param page {@link Page} + * @return {@link Page} + */ + default Page userPoConvertToUserListResult(org.springframework.data.domain.Page page) { + Page result = new Page<>(); + if (!CollectionUtils.isEmpty(page.getContent())) { + List list = new ArrayList<>(); + for (UserPO user : page.getContent()) { + UserListResult userListResult = userPoConvertToUserListResult(user); + if (StringUtils.hasText(user.getPhone())) { + userListResult.setPhone((StringUtils.hasText(user.getPhoneAreaCode()) + ? "+" + user.getPhoneAreaCode() + : "") + user.getPhone()); + } + list.add(userListResult); + } + //@formatter:off + result.setPagination(Page.Pagination.builder() + .total(page.getTotalElements()) + .totalPages(page.getTotalPages()) + .current(page.getPageable().getPageNumber() + 1) + .build()); + //@formatter:on + result.setList(list); + } + return result; + } + + /** + * 用户创建参数转换为用户实体 + * + * @param param {@link UserCreateParam} + * @return {@link UserEntity} + */ + default UserEntity userCreateParamConvertToUserEntity(UserCreateParam param) { + if (param == null) { + return null; + } + UserEntity userEntity = new UserEntity(); + userEntity.setRemark(param.getRemark()); + userEntity.setUsername(param.getUsername()); + userEntity.setEmail(param.getEmail()); + if (StringUtils.hasText(param.getPhone())) { + userEntity.setPhone(getPhoneNumber(param.getPhone())); + userEntity.setPhoneAreaCode(getPhoneAreaCode(param.getPhone())); + } + userEntity.setFullName(param.getFullName()); + userEntity.setNickName(param.getNickName()); + userEntity.setLastUpdatePasswordTime(LocalDateTime.now()); + userEntity.setStatus(cn.topiam.employee.common.enums.UserStatus.ENABLE); + userEntity.setAvatar("https://joeschmoe.io/api/v1/random"); + userEntity.setDataOrigin(cn.topiam.employee.common.enums.DataOrigin.INPUT); + userEntity.setEmailVerified(Boolean.FALSE); + userEntity.setExpireDate( + java.util.Objects.isNull(param.getExpireDate()) ? java.time.LocalDate.of(2116, 12, 31) + : param.getExpireDate()); + userEntity.setAuthTotal(0L); + userEntity.setPassword(ApplicationContextHelp + .getBean(org.springframework.security.crypto.password.PasswordEncoder.class) + .encode(param.getPassword())); + + return userEntity; + } + + /** + * 用户更新参数转换为用户实体类 + * + * @param param {@link UserUpdateParam} 更新参数 + * @return {@link UserEntity} 用户实体 + */ + default UserEntity userUpdateParamConvertToUserEntity(UserUpdateParam param) { + if (param == null) { + return null; + } + UserEntity userEntity = new UserEntity(); + if (param.getId() != null) { + userEntity.setId(Long.parseLong(param.getId())); + } + userEntity.setRemark(param.getRemark()); + userEntity.setEmail(param.getEmail()); + if (StringUtils.hasText(param.getPhone())) { + userEntity.setPhone(getPhoneNumber(param.getPhone())); + userEntity.setPhoneAreaCode(getPhoneAreaCode(param.getPhone())); + } + userEntity.setFullName(param.getFullName()); + userEntity.setNickName(param.getNickName()); + userEntity.setAvatar(param.getAvatar()); + userEntity.setStatus(param.getStatus()); + userEntity.setExpireDate(param.getExpireDate()); + return userEntity; + } + + /** + * 实体转为用户详情返回 + * + * @param user {@link UserEntity} + * @param detail {@link UserDetailEntity} + * @return {@link UserEntity} 用户详情 + */ + @Mapping(target = "id", source = "user.id") + @Mapping(target = "username", source = "user.username") + @Mapping(target = "dataOrigin", source = "user.dataOrigin.code") + @Mapping(target = "emailVerified", source = "user.emailVerified") + @Mapping(target = "phoneVerified", source = "user.phoneVerified") + @Mapping(target = "expireDate", source = "user.expireDate") + @Mapping(target = "remark", source = "user.remark") + @Mapping(target = "createTime", source = "user.createTime") + @Mapping(target = "externalId", source = "user.externalId") + @Mapping(target = "updateTime", source = "user.updateTime") + @Mapping(target = "status", source = "user.status.code") + @Mapping(target = "phone", ignore = true) + @Mapping(target = "avatar", source = "user.avatar") + @Mapping(target = "email", source = "user.email") + @Mapping(target = "nickName", source = "user.nickName") + @Mapping(target = "lastAuthIp", source = "user.lastAuthIp") + @Mapping(target = "lastAuthTime", source = "user.lastAuthTime") + @Mapping(target = "fullName", source = "user.fullName") + @Mapping(target = "idCard", source = "detail.idCard") + @Mapping(target = "address", source = "detail.address") + @Mapping(target = "authTotal", source = "user.authTotal", defaultValue = "0") + UserResult entityConvertToUserResult(UserEntity user, UserDetailEntity detail); + + /** + * 用户详情修改入参转换用户详情实体 + * + * @param param {@link UserUpdateParam} + * @return {@link UserDetailEntity} + */ + @Mapping(target = "website", ignore = true) + @Mapping(target = "idType", ignore = true) + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + @Mapping(target = "userId", source = "id") + UserDetailEntity userUpdateParamConvertToUserDetailsEntity(UserUpdateParam param); + + /** + * 创建用户入参转用户详情 + * + * @param param {@link UserCreateParam} + * @return {@link UserDetailEntity} + */ + @Mapping(target = "website", ignore = true) + @Mapping(target = "idType", ignore = true) + @Mapping(target = "userId", ignore = true) + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "idCard", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + @Mapping(target = "address", ignore = true) + UserDetailEntity userCreateParamConvertToUserDetailEntity(UserCreateParam param); + + /** + * 审计列表请求到本机搜索查询 + * + * @param id {@link Long} + * @param page {@link PageModel} + * @return {@link NativeSearchQuery} + */ + default NativeSearchQuery auditListRequestConvertToNativeSearchQuery(Long id, PageModel page) { + //构建查询 builder下有 must、should 以及 mustNot 相当于 sql 中的 and、or 以及 not + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + Collection> fieldSortBuilders = Lists.newArrayList(); + //事件类型 + Set set = Sets.newHashSet(); + set.add(LOGIN_PORTAL.getCode()); + set.add(EventType.APP_SSO.getCode()); + queryBuilder.must(QueryBuilders.termsQuery(EVENT_TYPE, set)); + //用户id + queryBuilder.must(QueryBuilders.queryStringQuery(id.toString()).field(ACTOR_ID)); + //字段排序 + page.getSorts().forEach(sort -> { + FieldSortBuilder eventTimeSortBuilder = SortBuilders.fieldSort(EVENT_TIME) + .order(SortOrder.DESC); + if (org.apache.commons.lang3.StringUtils.equals(sort.getSorter(), SORT_EVENT_TIME)) { + if (sort.getAsc()) { + eventTimeSortBuilder.order(SortOrder.ASC); + } + } + fieldSortBuilders.add(eventTimeSortBuilder); + }); + return new NativeSearchQueryBuilder().withQuery(queryBuilder) + //分页参数 + .withPageable(PageRequest.of(page.getCurrent(), page.getPageSize())) + //排序 + .withSorts(fieldSortBuilders).build(); + } + + /** + * searchHits 转用户登录日志列表 + * + * @param search {@link SearchHits} + * @param page {@link PageModel} + * @return {@link Page} + */ + default Page searchHitsConvertToAuditListResult(SearchHits search, + PageModel page) { + List list = new ArrayList<>(); + //总记录数 + search.forEach(hit -> { + AuditElasticSearchEntity content = hit.getContent(); + Event event = content.getEvent(); + UserLoginAuditListResult result = new UserLoginAuditListResult(); + //单点登录 + if (event.getType().getCode().equals(AppEventType.APP_SSO.getCode())) { + result.setAppName(getAppName(content.getTargets().get(0).getId())); + } + //登录门户 + if (event.getType().getCode().equals(AuthenticationEventType.LOGIN_PORTAL.getCode())) { + result.setAppName(PORTAL.getDesc()); + } + result.setEventTime(LocalDateTime.ofInstant(event.getTime(), ZoneId.systemDefault())); + result.setClientIp(content.getGeoLocation().getIp()); + result.setBrowser(content.getUserAgent().getBrowser()); + result.setLocation(content.getGeoLocation().getCityName()); + result.setEventStatus(event.getStatus()); + list.add(result); + }); + //@formatter:off + Page result = new Page<>(); + result.setPagination(Page.Pagination.builder() + .total(search.getTotalHits()) + .totalPages(Math.toIntExact(search.getTotalHits() / page.getPageSize())) + .current(page.getCurrent() + 1) + .build()); + result.setList(list); + //@formatter:on + return result; + } + + /** + * 用户实体转换为用户分页结果 + * + * @param po {@link UserEntity} + * @return {@link UserListResult} + */ + @Mapping(target = "status", source = "status.code") + @Mapping(target = "phone", ignore = true) + @Mapping(target = "authTotal", defaultValue = "0L", source = "authTotal") + @Mapping(target = "dataOrigin", source = "dataOrigin.code") + UserListResult userPoConvertToUserListResult(UserPO po); + + /** + * 获取应用名称 + * + * @param targetId {@link String} + * @return {@link String} + */ + private String getAppName(String targetId) { + if (!StringUtils.hasText(targetId)) { + return null; + } + AppRepository repository = ApplicationContextHelp.getBean(AppRepository.class); + AppEntity app = repository.findById(Long.valueOf(targetId)).orElse(new AppEntity()); + return app.getName(); + } + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/account/UserGroupConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/account/UserGroupConverter.java new file mode 100644 index 00000000..9ebfa027 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/account/UserGroupConverter.java @@ -0,0 +1,171 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.account; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.util.CollectionUtils; + +import com.google.common.collect.Lists; +import com.querydsl.core.types.ExpressionUtils; +import com.querydsl.core.types.Predicate; + +import cn.topiam.employee.common.entity.account.QUserGroupEntity; +import cn.topiam.employee.common.entity.account.UserEntity; +import cn.topiam.employee.common.entity.account.UserGroupEntity; +import cn.topiam.employee.common.entity.account.po.UserPO; +import cn.topiam.employee.console.pojo.query.account.UserGroupListQuery; +import cn.topiam.employee.console.pojo.result.account.UserGroupListResult; +import cn.topiam.employee.console.pojo.result.account.UserGroupMemberListResult; +import cn.topiam.employee.console.pojo.result.account.UserGroupResult; +import cn.topiam.employee.console.pojo.save.account.UserGroupCreateParam; +import cn.topiam.employee.console.pojo.update.account.UserGroupUpdateParam; +import cn.topiam.employee.support.context.ApplicationContextHelp; +import cn.topiam.employee.support.repository.page.domain.Page; + +/** + * 用户映射 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/14 22:45 + */ +@Mapper(componentModel = "spring") +public interface UserGroupConverter { + + /** + * 用户实体转换为用户分页结果 + * + * @param page {@link Page} + * @return {@link Page} + */ + default Page userGroupEntityConvertToUserGroupResult(org.springframework.data.domain.Page page) { + Page result = new Page<>(); + if (!CollectionUtils.isEmpty(page.getContent())) { + List list = new ArrayList<>(); + for (UserGroupEntity user : page.getContent()) { + UserGroupConverter bean = ApplicationContextHelp.getBean(UserGroupConverter.class); + list.add(bean.entityConvertToUserGroupPaginationResult(user)); + } + + //@formatter:off + result.setPagination(Page.Pagination.builder() + .total(page.getTotalElements()) + .totalPages(page.getTotalPages()) + .current(page.getPageable().getPageNumber() + 1) + .build()); + //@formatter:on + result.setList(list); + } + return result; + } + + /** + * 实体转换为群组成员列表结果 + * + * @param users {@link List} + * @return {@link List} + */ + default Page userPoConvertToGroupMemberListResult(org.springframework.data.domain.Page users) { + Page page = new Page<>(); + List list = Lists.newArrayList(); + for (UserPO entity : users.getContent()) { + list.add(userPoConvertToGroupMemberListResult(entity)); + } + page.setList(list); + //@formatter:off + page.setPagination(Page.Pagination.builder() + .total(users.getTotalElements()) + .totalPages(users.getTotalPages()) + .current(users.getPageable().getPageNumber() + 1) + .build()); + //@formatter:on + return page; + } + + /** + * 用户实体转换为用户分页结果 + * + * @param page {@link UserGroupEntity} + * @return {@link UserGroupListResult} + */ + UserGroupListResult entityConvertToUserGroupPaginationResult(UserGroupEntity page); + + /** + * 用户创建参数转换为用户实体 + * + * @param param {@link UserGroupCreateParam} + * @return {@link UserEntity} + */ + @Mapping(target = "id", ignore = true) + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + UserGroupEntity userGroupCreateParamConvertToEntity(UserGroupCreateParam param); + + /** + * 用户更新参数转换为用户实体类 + * + * @param param {@link UserGroupUpdateParam} 更新参数 + * @return {@link UserEntity} 用户实体 + */ + + @Mapping(target = "id", ignore = true) + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + UserGroupEntity userGroupUpdateParamConvertToEntity(UserGroupUpdateParam param); + + /** + * 实体转用户群组列表 + * + * @param user {@link UserPO} + * @return {@link UserGroupMemberListResult} + */ + UserGroupMemberListResult userPoConvertToGroupMemberListResult(UserPO user); + + /** + * 查询用户组列表参数转换为 Querydsl Predicate + * + * @param query {@link UserGroupListQuery} query + * @return {@link Predicate} + */ + default Predicate queryUserGroupListParamConvertToPredicate(UserGroupListQuery query) { + QUserGroupEntity userGroup = QUserGroupEntity.userGroupEntity; + Predicate predicate = userGroup.isNotNull(); + //查询条件 + //@formatter:off + predicate = StringUtils.isBlank(query.getName()) ? predicate : ExpressionUtils.and(predicate, userGroup.name.like("%" + query.getName() + "%")); + predicate = StringUtils.isBlank(query.getCode()) ? predicate : ExpressionUtils.and(predicate, userGroup.code.eq(query.getCode())); + //@formatter:on + return predicate; + } + + /** + * 实体转用户详情 + * + * @param details {@link UserGroupEntity} + * @return {@link UserGroupResult} + */ + UserGroupResult entityConvertToUserGroupResult(UserGroupEntity details); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppAccessPolicyConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppAccessPolicyConverter.java new file mode 100644 index 00000000..6efc1672 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppAccessPolicyConverter.java @@ -0,0 +1,94 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.app; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.Mapper; +import org.springframework.util.CollectionUtils; + +import cn.topiam.employee.common.entity.app.AppAccessPolicyEntity; +import cn.topiam.employee.common.entity.app.po.AppAccessPolicyPO; +import cn.topiam.employee.console.pojo.result.app.AppAccessPolicyResult; +import cn.topiam.employee.console.pojo.save.app.AppAccessPolicyCreateParam; +import cn.topiam.employee.support.repository.page.domain.Page; + +/** + * 应用授权策略 Converter + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/4 19:57 + */ +@Mapper(componentModel = "spring") +public interface AppAccessPolicyConverter { + + /** + * 应用授权策略分页列表转换为应用授权策略分页结果 + * + * @param page {@link AppAccessPolicyEntity} + * @return {@link AppAccessPolicyResult} + */ + default Page appPolicyEntityListConvertToAppPolicyResult(org.springframework.data.domain.Page page) { + Page result = new Page<>(); + if (!CollectionUtils.isEmpty(page.getContent())) { + List list = new ArrayList<>(); + for (AppAccessPolicyPO po : page.getContent()) { + list.add(entityConvertToAppPolicyResult(po)); + } + //@formatter:off + result.setPagination(Page.Pagination.builder() + .total(page.getTotalElements()) + .totalPages(page.getTotalPages()) + .current(page.getPageable().getPageNumber() + 1) + .build()); + //@formatter:on + result.setList(list); + } + return result; + } + + /** + * 应用授权策略实体转换为应用授权策略结果 + * + * @param entity {@link AppAccessPolicyEntity} + * @return {@link AppAccessPolicyEntity} + */ + AppAccessPolicyResult entityConvertToAppPolicyResult(AppAccessPolicyPO entity); + + /** + * 应用授权策略添加参数转换为应用授权策略实体 + * + * @param param {@link AppAccessPolicyCreateParam} + * @return {@link AppAccessPolicyEntity} + */ + default List appPolicyCreateParamConvertToEntity(AppAccessPolicyCreateParam param) { + if (param == null) { + return new ArrayList<>(); + } + List list = new ArrayList<>(); + for (String subjectId : param.getSubjectIds()) { + AppAccessPolicyEntity entity = new AppAccessPolicyEntity(); + entity.setAppId(Long.valueOf(param.getAppId())); + entity.setSubjectType(param.getSubjectType()); + entity.setSubjectId(subjectId); + list.add(entity); + } + return list; + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppAccountConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppAccountConverter.java new file mode 100644 index 00000000..463551d7 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppAccountConverter.java @@ -0,0 +1,89 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.app; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.util.CollectionUtils; + +import cn.topiam.employee.common.entity.app.AppAccountEntity; +import cn.topiam.employee.common.entity.app.po.AppAccountPO; +import cn.topiam.employee.console.pojo.result.app.AppAccountListResult; +import cn.topiam.employee.console.pojo.save.app.AppAccountCreateParam; +import cn.topiam.employee.support.repository.page.domain.Page; + +/** + * 应用账户映射 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/4 19:08 + */ +@Mapper(componentModel = "spring") +public interface AppAccountConverter { + + /** + * 应用账户分页实体转换应用账户分页结果 + * + * @param page {@link Page} + * @return {@link Page} + */ + default Page appAccountEntityConvertToAppAccountResult(org.springframework.data.domain.Page page) { + Page result = new Page<>(); + if (!CollectionUtils.isEmpty(page.getContent())) { + List list = new ArrayList<>(); + for (AppAccountPO po : page.getContent()) { + list.add(entityConvertToAppAccountResult(po)); + } + //@formatter:off + result.setPagination(Page.Pagination.builder() + .total(page.getTotalElements()) + .totalPages(page.getTotalPages()) + .current(page.getPageable().getPageNumber() + 1) + .build()); + //@formatter:on + result.setList(list); + } + return result; + } + + /** + * 应用账户实体转换为应用账户结果 + * + * @param appAccountPo {@link AppAccountPO} + * @return {@link AppAccountListResult} + */ + AppAccountListResult entityConvertToAppAccountResult(AppAccountPO appAccountPo); + + /** + * 应用账户新增参数转换应用账户实体 + * + * @param param {@link AppAccountCreateParam} + * @return {@link AppAccountEntity} + */ + @Mapping(target = "remark", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + AppAccountEntity appAccountCreateParamConvertToEntity(AppAccountCreateParam param); + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppCertConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppCertConverter.java new file mode 100644 index 00000000..3ecb3e6c --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppCertConverter.java @@ -0,0 +1,81 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.app; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; +import org.mapstruct.Mapper; + +import com.querydsl.core.types.ExpressionUtils; +import com.querydsl.core.types.Predicate; + +import cn.topiam.employee.common.entity.app.AppCertEntity; +import cn.topiam.employee.common.entity.app.QAppCertEntity; +import cn.topiam.employee.console.pojo.query.app.AppCertQuery; +import cn.topiam.employee.console.pojo.result.app.AppCertListResult; + +/** + * 应用证书Converter + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/4 23:52 + */ +@Mapper(componentModel = "spring") +public interface AppCertConverter { + /** + * 查询应用列表参数转换为 Querydsl Predicate + * + * @param query {@link AppCertQuery} query + * @return {@link Predicate} + */ + default Predicate queryAppCertListParamConvertToPredicate(AppCertQuery query) { + QAppCertEntity cert = QAppCertEntity.appCertEntity; + Predicate predicate = cert.isNotNull(); + //查询条件 + //@formatter:off + predicate = StringUtils.isBlank(query.getAppId()) ? predicate : ExpressionUtils.and(predicate, cert.appId.eq(Long.valueOf(query.getAppId()))); + predicate = Objects.isNull(query.getUsingType()) ? predicate : ExpressionUtils.and(predicate, cert.usingType.eq(query.getUsingType())); + //@formatter:on + return predicate; + } + + /** + * 实体转换为应用程序证书列表结果 + * + * @param list {@link List} + * @return {@link List} + */ + default List entityConvertToAppCertListResult(List list) { + List results = new ArrayList<>(); + for (AppCertEntity cert : list) { + results.add(entityConvertToAppCertListResult(cert)); + } + return results; + } + + /** + * 实体转换为应用程序证书列表结果 + * + * @param list {@link List} + * @return {@link List} + */ + AppCertListResult entityConvertToAppCertListResult(AppCertEntity list); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppConverter.java new file mode 100644 index 00000000..e91b3501 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppConverter.java @@ -0,0 +1,199 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.app; + +import java.util.List; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.util.AlternativeJdkIdGenerator; +import org.springframework.util.IdGenerator; + +import com.google.common.collect.Lists; +import com.querydsl.core.types.ExpressionUtils; +import com.querydsl.core.types.Predicate; + +import cn.topiam.employee.application.ApplicationService; +import cn.topiam.employee.application.ApplicationServiceLoader; +import cn.topiam.employee.common.entity.app.AppEntity; +import cn.topiam.employee.common.entity.app.QAppEntity; +import cn.topiam.employee.console.pojo.query.app.AppQuery; +import cn.topiam.employee.console.pojo.result.app.AppGetResult; +import cn.topiam.employee.console.pojo.result.app.AppListResult; +import cn.topiam.employee.console.pojo.update.app.AppUpdateParam; +import cn.topiam.employee.support.context.ApplicationContextHelp; +import cn.topiam.employee.support.repository.page.domain.Page; + +/** + * 应用映射 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/14 22:45 + */ +@Mapper(componentModel = "spring") +public interface AppConverter { + + /** + * 查询应用列表参数转换为 Querydsl Predicate + * + * @param query {@link AppQuery} query + * @return {@link Predicate} + */ + default Predicate queryAppListParamConvertToPredicate(AppQuery query) { + QAppEntity application = QAppEntity.appEntity; + Predicate predicate = application.isNotNull(); + //查询条件 + //@formatter:off + predicate = StringUtils.isBlank(query.getName()) ? predicate : ExpressionUtils.and(predicate, application.name.like("%" + query.getName() + "%")); + predicate = Objects.isNull(query.getProtocol()) ? predicate : ExpressionUtils.and(predicate, application.protocol.eq(query.getProtocol())); + //@formatter:on + return predicate; + } + + /** + * 实体转换为应用列表结果 + * + * @param entityPage {@link List} + * @return {@link List} + */ + default Page entityConvertToAppListResult(org.springframework.data.domain.Page entityPage) { + Page page = new Page<>(); + List list = Lists.newArrayList(); + for (AppEntity entity : entityPage.getContent()) { + AppListResult result = entityConvertToAppListResult(entity); + // 如果备注为空、放置详情,由于 entityConvertToAppListResult 为共用,所以这里单独处理。 + if (StringUtils.isBlank(result.getRemark())) { + ApplicationServiceLoader loader = ApplicationContextHelp + .getBean(ApplicationServiceLoader.class); + ApplicationService applicationService = loader + .getApplicationService(entity.getTemplate()); + if (!Objects.isNull(applicationService)) { + result.setRemark(applicationService.getDescription()); + } + } + list.add(result); + } + page.setList(list); + //@formatter:off + page.setPagination(Page.Pagination.builder() + .total(entityPage.getTotalElements()) + .totalPages(entityPage.getTotalPages()) + .current(entityPage.getPageable().getPageNumber() + 1) + .build()); + //@formatter:on + return page; + } + + /** + * 实体转应用管理列表 + * + * @param entity {@link AppEntity} + * @return {@link AppListResult} + */ + default AppListResult entityConvertToAppListResult(AppEntity entity) { + if (entity == null) { + return null; + } + + AppListResult appListResult = new AppListResult(); + if (entity.getId() != null) { + appListResult.setId(String.valueOf(entity.getId())); + } + appListResult.setName(entity.getName()); + appListResult.setType(entity.getType()); + appListResult.setIcon(entity.getIcon()); + //图标未配置,所以先从模版中拿 + if (StringUtils.isBlank(entity.getIcon())) { + ApplicationService applicationService = getApplicationServiceLoader() + .getApplicationService(entity.getTemplate()); + appListResult.setIcon(applicationService.getBase64Icon()); + } + appListResult.setTemplate(entity.getTemplate()); + appListResult.setProtocol(entity.getProtocol()); + appListResult.setEnabled(entity.getEnabled()); + appListResult.setRemark(entity.getRemark()); + return appListResult; + } + + /** + * 实体转应用返回 + * + * @param entity {@link AppEntity} + * @return {@link AppGetResult} + */ + default AppGetResult entityConvertToAppResult(AppEntity entity) { + if (entity == null) { + return null; + } + AppGetResult appGetResult = new AppGetResult(); + + if (entity.getId() != null) { + appGetResult.setId(String.valueOf(entity.getId())); + } + appGetResult.setName(entity.getName()); + appGetResult.setClientId(entity.getClientId()); + appGetResult.setClientSecret(entity.getClientSecret()); + appGetResult.setType(entity.getType()); + appGetResult.setIcon(entity.getIcon()); + appGetResult.setTemplate(entity.getTemplate()); + appGetResult.setProtocol(entity.getProtocol()); + appGetResult.setProtocolName(entity.getProtocol().getDesc()); + appGetResult.setEnabled(entity.getEnabled()); + appGetResult.setCreateTime(entity.getCreateTime()); + appGetResult.setRemark(entity.getRemark()); + return appGetResult; + } + + /** + * 将应用修改对象转换为entity + * + * @param param {@link AppUpdateParam} + * @return {@link AppEntity} + */ + @Mapping(target = "code", ignore = true) + @Mapping(target = "initLoginUrl", ignore = true) + @Mapping(target = "clientSecret", ignore = true) + @Mapping(target = "clientId", ignore = true) + @Mapping(target = "initLoginType", ignore = true) + @Mapping(target = "authorizationType", ignore = true) + @Mapping(target = "template", ignore = true) + @Mapping(target = "type", ignore = true) + @Mapping(target = "protocol", ignore = true) + @Mapping(target = "enabled", ignore = true) + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + AppEntity appUpdateParamConverterToEntity(AppUpdateParam param); + + /** + * IdGenerator + */ + IdGenerator ID_GENERATOR = new AlternativeJdkIdGenerator(); + + /** + * 获取 ApplicationServiceLoader + * + * @return {@link ApplicationServiceLoader} + */ + private ApplicationServiceLoader getApplicationServiceLoader() { + return ApplicationContextHelp.getBean(ApplicationServiceLoader.class); + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppPermissionActionConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppPermissionActionConverter.java new file mode 100644 index 00000000..3c33dc3a --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppPermissionActionConverter.java @@ -0,0 +1,125 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.app; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.util.ObjectUtils; + +import com.querydsl.core.types.ExpressionUtils; +import com.querydsl.core.types.Predicate; + +import cn.topiam.employee.common.entity.app.AppPermissionActionEntity; +import cn.topiam.employee.common.entity.app.AppPermissionResourceEntity; +import cn.topiam.employee.common.entity.app.QAppPermissionResourceEntity; +import cn.topiam.employee.common.enums.PermissionActionType; +import cn.topiam.employee.console.pojo.query.app.AppPermissionActionListQuery; +import cn.topiam.employee.console.pojo.result.app.AppPermissionActionListResult; + +/** + * 权限映射 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/14 22:45 + */ +@Mapper(componentModel = "spring") +public interface AppPermissionActionConverter { + /** + * 应用权限资源列表转分页 + * + * @param query {@link AppPermissionActionListQuery} + * @return {@link Predicate} + */ + default Predicate appPermissionActionListQueryConvertToPredicate(AppPermissionActionListQuery query) { + QAppPermissionResourceEntity resource = QAppPermissionResourceEntity.appPermissionResourceEntity; + Predicate predicate = resource.isNotNull(); + //查询条件 + //@formatter:off + // 资源名称 + predicate = StringUtils.isBlank(query.getName()) ? predicate : ExpressionUtils.and(predicate, resource.name.like("%" + query.getName() + "%")); + // 资源ID + predicate = ObjectUtils.isEmpty(query.getId()) ? predicate : ExpressionUtils.and(predicate, resource.id.eq(Long.valueOf(query.getId()))); + //应用ID + predicate = ObjectUtils.isEmpty(query.getAppId()) ? predicate : ExpressionUtils.and(predicate, resource.appId.eq(Long.valueOf(query.getAppId()))); + //@formatter:on + return predicate; + } + + /** + * 实体转资源权限结果返回 + * + * @param list {@link AppPermissionResourceEntity} + * @return {@link AppPermissionActionListResult} + */ + default List entityConvertToResourceActionListResult(List list) { + List results = new ArrayList<>(); + List menus = new ArrayList<>(); + List apis = new ArrayList<>(); + List buttons = new ArrayList<>(); + List others = new ArrayList<>(); + List datas = new ArrayList<>(); + for (AppPermissionResourceEntity resource : list) { + for (AppPermissionActionEntity action : resource.getActions()) { + if (PermissionActionType.MENU.equals(action.getType())) { + menus.add(actionConvertToResourceActionResult(action)); + } + if (PermissionActionType.API.equals(action.getType())) { + apis.add(actionConvertToResourceActionResult(action)); + } + if (PermissionActionType.DATA.equals(action.getType())) { + datas.add(actionConvertToResourceActionResult(action)); + } + if (PermissionActionType.BUTTON.equals(action.getType())) { + buttons.add(actionConvertToResourceActionResult(action)); + } + if (PermissionActionType.OTHER.equals(action.getType())) { + others.add(actionConvertToResourceActionResult(action)); + } + } + AppPermissionActionListResult result = new AppPermissionActionListResult(); + //基本信息 + result.setAppId(resource.getAppId().toString()); + result.setId(resource.getId().toString()); + result.setName(resource.getName()); + result.setEnabled(resource.getEnabled()); + result.setDesc(resource.getDesc()); + //权限资源 + result.setButtons(buttons); + result.setApis(apis); + result.setDatas(datas); + result.setMenus(menus); + result.setOthers(others); + results.add(result); + } + return results; + } + + /** + * actionConvertToResourceActionResult + * + * @param action {@link AppPermissionActionEntity} + * @return {@link AppPermissionActionListResult.Action} + */ + @Mapping(target = "access", source = "value") + AppPermissionActionListResult.Action actionConvertToResourceActionResult(AppPermissionActionEntity action); + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppPermissionPolicyConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppPermissionPolicyConverter.java new file mode 100644 index 00000000..6a5933ce --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppPermissionPolicyConverter.java @@ -0,0 +1,103 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.app; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.util.CollectionUtils; + +import cn.topiam.employee.common.entity.app.AppPermissionPolicyEntity; +import cn.topiam.employee.common.entity.app.po.AppPermissionPolicyPO; +import cn.topiam.employee.console.pojo.result.app.AppPermissionPolicyListResult; +import cn.topiam.employee.console.pojo.save.app.AppPermissionPolicyCreateParam; +import cn.topiam.employee.console.pojo.update.app.AppPermissionPolicyUpdateParam; +import cn.topiam.employee.support.repository.page.domain.Page; + +/** + * 策略映射 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/14 22:45 + */ +@Mapper(componentModel = "spring", uses = AppPermissionActionConverter.class) +public interface AppPermissionPolicyConverter { + + /** + * 资源创建参数转实体类 + * + * @param param {@link AppPermissionPolicyCreateParam} + * @return {@link AppPermissionPolicyEntity} + */ + @Mapping(target = "id", ignore = true) + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "remark", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + AppPermissionPolicyEntity policyCreateParamConvertToEntity(AppPermissionPolicyCreateParam param); + + /** + * 资源修改参数转实体类 + * + * @param param {@link AppPermissionPolicyCreateParam} + * @return {@link AppPermissionPolicyEntity} + */ + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "remark", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + AppPermissionPolicyEntity policyUpdateParamConvertToEntity(AppPermissionPolicyUpdateParam param); + + /** + * 资源转换为资源列表结果 + * + * @param page {@link Page} + * @return {@link Page} + */ + default Page entityConvertToPolicyListResult(org.springframework.data.domain.Page page) { + Page result = new Page<>(); + List pageList = page.getContent(); + if (!CollectionUtils.isEmpty(pageList)) { + //@formatter:off + result.setPagination(Page.Pagination.builder() + .total(page.getTotalElements()) + .totalPages(page.getTotalPages()) + .current(page.getPageable().getPageNumber() + 1) + .build()); + //@formatter:on + List list = new ArrayList<>(); + for (AppPermissionPolicyPO po : pageList) { + list.add(entityConvertToPolicyListResult(po)); + } + result.setList(list); + } + return result; + } + + /** + * entityConvertToPolicyListResult + * + * @param entity {@link AppPermissionPolicyListResult} + * @return {@link AppPermissionPolicyPO} + */ + AppPermissionPolicyListResult entityConvertToPolicyListResult(AppPermissionPolicyPO entity); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppPermissionResourceConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppPermissionResourceConverter.java new file mode 100644 index 00000000..07c9988b --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppPermissionResourceConverter.java @@ -0,0 +1,140 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.app; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; + +import com.querydsl.core.types.ExpressionUtils; +import com.querydsl.core.types.Predicate; + +import cn.topiam.employee.common.entity.app.AppPermissionResourceEntity; +import cn.topiam.employee.common.entity.app.QAppPermissionResourceEntity; +import cn.topiam.employee.console.pojo.query.app.AppResourceListQuery; +import cn.topiam.employee.console.pojo.result.app.AppPermissionResourceGetResult; +import cn.topiam.employee.console.pojo.result.app.AppPermissionResourceListResult; +import cn.topiam.employee.console.pojo.save.app.AppPermissionResourceCreateParam; +import cn.topiam.employee.console.pojo.update.app.AppPermissionResourceUpdateParam; +import cn.topiam.employee.support.repository.page.domain.Page; + +/** + * 资源映射 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/14 22:45 + */ +@Mapper(componentModel = "spring", uses = AppPermissionActionConverter.class) +public interface AppPermissionResourceConverter { + + /** + * 资源分页查询参数转实体 + * + * @param query {@link AppResourceListQuery} + * @return {@link Predicate} + */ + default Predicate resourcePaginationParamConvertToPredicate(AppResourceListQuery query) { + QAppPermissionResourceEntity resource = QAppPermissionResourceEntity.appPermissionResourceEntity; + Predicate predicate = resource.isNotNull(); + //查询条件 + //@formatter:off + // 资源名称 + predicate = StringUtils.isBlank(query.getName()) ? predicate : ExpressionUtils.and(predicate, resource.name.like("%" + query.getName() + "%")); + // 所属应用 + predicate = ObjectUtils.isEmpty(query.getAppId()) ? predicate : ExpressionUtils.and(predicate, resource.appId.eq(query.getAppId())); + //@formatter:on + return predicate; + } + + /** + * 资源创建参数转实体类 + * + * @param param {@link AppPermissionResourceCreateParam} + * @return {@link AppPermissionResourceEntity} + */ + @Mapping(target = "actions", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "remark", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + AppPermissionResourceEntity resourceCreateParamConvertToEntity(AppPermissionResourceCreateParam param); + + /** + * 资源修改参数转实体类 + * + * @param param {@link AppPermissionResourceCreateParam} + * @return {@link AppPermissionResourceEntity} + */ + @Mapping(target = "actions", ignore = true) + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "remark", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + AppPermissionResourceEntity resourceUpdateParamConvertToEntity(AppPermissionResourceUpdateParam param); + + /** + * 资源转换为资源列表结果 + * + * @param page {@link Page} + * @return {@link Page} + */ + default Page entityConvertToResourceListResult(org.springframework.data.domain.Page page) { + Page result = new Page<>(); + List pageList = page.getContent(); + if (!CollectionUtils.isEmpty(pageList)) { + List list = new ArrayList<>(); + for (AppPermissionResourceEntity resource : pageList) { + list.add(entityConvertToResourceListResult(resource)); + } + //@formatter:off + result.setPagination(Page.Pagination.builder() + .total(page.getTotalElements()) + .totalPages(page.getTotalPages()) + .current(page.getPageable().getPageNumber() + 1) + .build()); + //@formatter:on + result.setList(list); + } + return result; + } + + /** + * 实体转换为资源列表结果 + * + * @param data {@link AppPermissionResourceEntity} + * @return {@link AppPermissionResourceListResult} + */ + AppPermissionResourceListResult entityConvertToResourceListResult(AppPermissionResourceEntity data); + + /** + * 实体转获取详情返回 + * + * @param resource {@link AppPermissionResourceEntity} + * @return {@link AppPermissionResourceGetResult} + */ + @Mapping(target = "actions", source = "actions") + AppPermissionResourceGetResult entityConvertToResourceGetResult(AppPermissionResourceEntity resource); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppPermissionRoleConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppPermissionRoleConverter.java new file mode 100644 index 00000000..9fc89dda --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/AppPermissionRoleConverter.java @@ -0,0 +1,141 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.app; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; + +import com.querydsl.core.types.ExpressionUtils; +import com.querydsl.core.types.Predicate; + +import cn.topiam.employee.common.entity.app.AppPermissionRoleEntity; +import cn.topiam.employee.common.entity.app.QAppPermissionRoleEntity; +import cn.topiam.employee.console.pojo.query.app.AppPermissionRoleListQuery; +import cn.topiam.employee.console.pojo.result.app.AppPermissionRoleListResult; +import cn.topiam.employee.console.pojo.result.app.AppPermissionRoleResult; +import cn.topiam.employee.console.pojo.save.app.AppPermissionRoleCreateParam; +import cn.topiam.employee.console.pojo.update.app.PermissionRoleUpdateParam; +import cn.topiam.employee.support.repository.page.domain.Page; + +/** + * 角色映射 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/14 22:45 + */ +@Mapper(componentModel = "spring") +public interface AppPermissionRoleConverter { + + /** + * 角色实体转换为角色分页结果 + * + * @param page {@link Page} + * @return {@link Page} + */ + default Page entityConvertToRolePaginationResult(org.springframework.data.domain.Page page) { + Page result = new Page<>(); + if (!CollectionUtils.isEmpty(page.getContent())) { + List list = new ArrayList<>(); + for (AppPermissionRoleEntity user : page.getContent()) { + list.add(entityConvertToRolePaginationResult(user)); + } + //@formatter:off + result.setPagination(Page.Pagination.builder() + .total(page.getTotalElements()) + .totalPages(page.getTotalPages()) + .current(page.getPageable().getPageNumber() + 1) + .build()); + //@formatter:on + result.setList(list); + } + return result; + } + + /** + * 角色实体转换为角色分页结果 + * + * @param page {@link AppPermissionRoleEntity} + * @return {@link AppPermissionRoleListResult} + */ + AppPermissionRoleListResult entityConvertToRolePaginationResult(AppPermissionRoleEntity page); + + /** + * 角色创建参数转换为角色实体 + * + * @param param {@link AppPermissionRoleCreateParam} + * @return {@link AppPermissionRoleEntity} + */ + @Mapping(target = "id", ignore = true) + @Mapping(target = "enabled", expression = "java(Boolean.TRUE)") + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + AppPermissionRoleEntity roleCreateParamConvertToEntity(AppPermissionRoleCreateParam param); + + /** + * 角色更新参数转换为角色实体类 + * + * @param param {@link PermissionRoleUpdateParam} 更新参数 + * @return {@link AppPermissionRoleEntity} 角色实体 + */ + @Mapping(target = "appId", ignore = true) + @Mapping(target = "enabled", ignore = true) + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + AppPermissionRoleEntity roleUpdateParamConvertToEntity(PermissionRoleUpdateParam param); + + /** + * 实体转系统详情结果 + * + * @param role {@link AppPermissionRoleEntity} + * @return {@link AppPermissionRoleResult} + */ + AppPermissionRoleResult entityConvertToRoleDetailResult(AppPermissionRoleEntity role); + + /** + * 角色分页查询参数转实体 + * + * @param query {@link AppPermissionRoleListQuery} + * @return {@link AppPermissionRoleEntity} + */ + default Predicate rolePaginationParamConvertToPredicate(AppPermissionRoleListQuery query) { + QAppPermissionRoleEntity role = QAppPermissionRoleEntity.appPermissionRoleEntity; + Predicate predicate = role.isNotNull(); + //查询条件 + //@formatter:off + // 角色名称 + predicate = StringUtils.isBlank(query.getName()) ? predicate : ExpressionUtils.and(predicate, role.name.like("%" + query.getName() + "%")); + // 是否启用 + predicate = ObjectUtils.isEmpty(query.getEnabled()) ? predicate : ExpressionUtils.and(predicate, role.enabled.eq(query.getEnabled())); + // 角色编码 + predicate = StringUtils.isBlank(query.getCode()) ? predicate : ExpressionUtils.and(predicate, role.code.eq(query.getCode())); + // 所属应用 + predicate = ObjectUtils.isEmpty(query.getAppId()) ? predicate : ExpressionUtils.and(predicate, role.appId.eq(query.getAppId())); + //@formatter:on + return predicate; + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/UserIdpBindConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/UserIdpBindConverter.java new file mode 100644 index 00000000..96d91bbc --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/app/UserIdpBindConverter.java @@ -0,0 +1,59 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.app; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.Mapper; + +import cn.topiam.employee.common.entity.account.po.UserIdpBindPo; +import cn.topiam.employee.console.pojo.result.app.UserIdpBindListResult; +import cn.topiam.employee.support.repository.page.domain.Page; + +/** + * 用户身份提供商绑定 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/11/3 19:08 + */ +@Mapper(componentModel = "spring") +public interface UserIdpBindConverter { + + /** + * 用户身份提供商绑定关系分页结果 + * + * @param page {@link Page} + * @return {@link Page} + */ + default List userIdpBindEntityConvertToUserIdpBindListResult(Iterable page) { + List list = new ArrayList<>(); + for (UserIdpBindPo entity : page) { + list.add(entityConvertToAppAccountResult(entity)); + } + return list; + } + + /** + * 用户身份提供商绑定关系转换结果 + * + * @param userIdpBindPo {@link UserIdpBindPo} + * @return {@link UserIdpBindListResult} + */ + UserIdpBindListResult entityConvertToAppAccountResult(UserIdpBindPo userIdpBindPo); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/authentication/IdentityProviderConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/authentication/IdentityProviderConverter.java new file mode 100644 index 00000000..b2ebfd55 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/authentication/IdentityProviderConverter.java @@ -0,0 +1,265 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.authentication; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import javax.validation.ConstraintViolationException; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.data.querydsl.QPageRequest; + +import com.alibaba.fastjson2.JSONObject; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.querydsl.core.types.ExpressionUtils; +import com.querydsl.core.types.Predicate; + +import cn.topiam.employee.authentication.common.config.IdentityProviderConfig; +import cn.topiam.employee.authentication.dingtalk.DingTalkIdpOauthConfig; +import cn.topiam.employee.authentication.dingtalk.DingTalkIdpScanCodeConfig; +import cn.topiam.employee.authentication.qq.QqIdpOauthConfig; +import cn.topiam.employee.authentication.wechat.WeChatIdpScanCodeConfig; +import cn.topiam.employee.authentication.wechatwork.WeChatWorkIdpScanCodeConfig; +import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity; +import cn.topiam.employee.common.entity.authentication.QIdentityProviderEntity; +import cn.topiam.employee.common.enums.IdentityProviderType; +import cn.topiam.employee.console.pojo.query.authentication.IdentityProviderListQuery; +import cn.topiam.employee.console.pojo.result.authentication.IdentityProviderListResult; +import cn.topiam.employee.console.pojo.result.authentication.IdentityProviderResult; +import cn.topiam.employee.console.pojo.save.authentication.IdentityProviderCreateParam; +import cn.topiam.employee.console.pojo.update.authentication.IdpUpdateParam; +import cn.topiam.employee.core.context.ServerContextHelp; +import cn.topiam.employee.support.exception.TopIamException; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.repository.page.domain.QueryDslRequest; +import cn.topiam.employee.support.validation.ValidationHelp; + +/** + * 身份提供商转换器 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/22 23:59 + */ +@Mapper(componentModel = "spring") +public interface IdentityProviderConverter { + + /** + * 认证源平台枚举转换器到认证源平台结果 + * + * @param values {@link List} + * @return {@link List} + */ + default Page entityConverterToIdentityProviderResult(org.springframework.data.domain.Page values) { + Page result = new Page<>(); + ArrayList list = new ArrayList<>(); + for (IdentityProviderEntity entity : values.getContent()) { + list.add(entityConverterToIdentityProviderResult(entity)); + } + //@formatter:off + result.setPagination(Page.Pagination.builder() + .total(values.getTotalElements()) + .totalPages(values.getTotalPages()) + .current(values.getPageable().getPageNumber() + 1) + .build()); + //@formatter:on + result.setList(list); + return result; + } + + /** + * 实体转认证源详情 + * + * @param entity {@link IdentityProviderEntity} + * @return {@link IdentityProviderListResult} + */ + @Mapping(target = "desc", source = "type.desc") + IdentityProviderListResult entityConverterToIdentityProviderResult(IdentityProviderEntity entity); + + /** + * 保存入参转换为实体类 + * + * @param param {@link IdentityProviderCreateParam} + * @return {@link IdentityProviderEntity} + */ + default IdentityProviderEntity identityProviderCreateParamConverterToEntity(IdentityProviderCreateParam param) { + if (param == null) { + return null; + } + if (!param.getCategory().getProviders().contains(param.getType())) { + throw new TopIamException("认证源类型与认证源提供商不匹配"); + } + try { + IdentityProviderConfig identityProviderConfig = getIdentityProviderConfig( + param.getType(), param.getConfig()); + ObjectMapper objectMapper = new ObjectMapper(); + // 指定序列化输入的类型 + objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), + ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); + //封装数据 + IdentityProviderEntity identityProviderEntity = new IdentityProviderEntity(); + identityProviderEntity.setName(param.getName()); + identityProviderEntity.setCode( + org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric(32).toLowerCase()); + identityProviderEntity.setType(param.getType()); + identityProviderEntity.setCategory(param.getCategory()); + identityProviderEntity.setDisplayed(param.getDisplayed()); + identityProviderEntity.setEnabled(Boolean.TRUE); + identityProviderEntity.setRemark(param.getRemark()); + //配置 + identityProviderEntity + .setConfig(objectMapper.writeValueAsString(identityProviderConfig)); + return identityProviderEntity; + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + /** + * entity转换为详细信息结果 + * + * @param entity {@link IdentityProviderEntity} + * @return {@link IdentityProviderListResult} + */ + default IdentityProviderResult entityConverterToIdentityProviderDetailResult(IdentityProviderEntity entity) { + if (entity == null) { + return null; + } + IdentityProviderResult result = new IdentityProviderResult(); + if (entity.getId() != null) { + result.setId(String.valueOf(entity.getId())); + } + result.setType(entity.getType()); + result.setDisplayed(entity.getDisplayed()); + result.setName(entity.getName()); + result.setRemark(entity.getRemark()); + //回调地址 + result.setRedirectUri(ServerContextHelp.getPortalPublicBaseUrl() + + entity.getType().getLoginPathPrefix() + "/" + entity.getCode()); + try { + ObjectMapper objectMapper = new ObjectMapper(); + // 指定序列化输入的类型 + objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), + ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); + IdentityProviderConfig config = objectMapper.readValue(entity.getConfig(), + IdentityProviderConfig.class); + result.setConfig(config); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + return result; + } + + /** + * 查询身份源列表参数转Predicate + * + * @param query {@link IdentityProviderListQuery} + * @param pageModel {@link PageModel} + * @return {@link QueryDslRequest} + */ + default QueryDslRequest queryIdentityProviderListParamConvertToPredicate(IdentityProviderListQuery query, + PageModel pageModel) { + QueryDslRequest request = new QueryDslRequest(); + QIdentityProviderEntity queryEntity = QIdentityProviderEntity.identityProviderEntity; + Predicate predicate = queryEntity.isNotNull(); + //查询条件 + //@formatter:off + predicate = Objects.isNull(query.getCategory()) ? predicate : ExpressionUtils.and(predicate, queryEntity.category.eq(query.getCategory())); + predicate = Objects.isNull(query.getName()) ? predicate : ExpressionUtils.and(predicate, queryEntity.name.eq(query.getName())); + //@formatter:on + request.setPredicate(predicate); + //分页条件 + request.setPageRequest(QPageRequest.of(pageModel.getCurrent(), pageModel.getPageSize())); + return request; + } + + /** + * 修改入参转换为实体类 + * + * @param param {@link IdpUpdateParam} + * @return {@link IdentityProviderEntity} + */ + default IdentityProviderEntity identityProviderUpdateParamConverterToEntity(IdpUpdateParam param) { + if (param == null) { + return null; + } + IdentityProviderConfig identityProviderConfig = getIdentityProviderConfig(param.getType(), + param.getConfig()); + ObjectMapper objectMapper = new ObjectMapper(); + // 指定序列化输入的类型 + objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), + ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); + try { + //封装数据 + IdentityProviderEntity identityProviderEntity = new IdentityProviderEntity(); + identityProviderEntity.setName(param.getName()); + identityProviderEntity.setType(param.getType()); + identityProviderEntity.setDisplayed(param.getDisplayed()); + identityProviderEntity.setRemark(param.getRemark()); + //配置 + identityProviderEntity + .setConfig(objectMapper.writeValueAsString(identityProviderConfig)); + return identityProviderEntity; + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + /** + * 获取认证源配置 + * + * @param type {@link IdentityProviderType} + * @param config {@link JSONObject} + * @return {@link IdentityProviderConfig} + */ + default IdentityProviderConfig getIdentityProviderConfig(IdentityProviderType type, + JSONObject config) { + //开始处理不同提供商的配置 + IdentityProviderConfig identityProviderConfig; + switch (type) { + //微信扫码 + case WECHAT_SCAN_CODE -> + identityProviderConfig = config.to(WeChatIdpScanCodeConfig.class); + //钉钉扫码 + case DINGTALK_SCAN_CODE -> + identityProviderConfig = config.to(DingTalkIdpScanCodeConfig.class); + //钉钉Oauth + case DINGTALK_OAUTH -> identityProviderConfig = config.to(DingTalkIdpOauthConfig.class); + //企业微信扫码 + case WECHATWORK_SCAN_CODE -> + identityProviderConfig = config.to(WeChatWorkIdpScanCodeConfig.class); + //QQ认证 + case QQ -> identityProviderConfig = config.to(QqIdpOauthConfig.class); + default -> throw new TopIamException("不支持此身份提供商"); + } + if (!Objects.nonNull(identityProviderConfig)) { + throw new NullPointerException("提供商配置不能为空"); + } + ValidationHelp.ValidationResult validationResult = ValidationHelp + .validateEntity(identityProviderConfig); + if (validationResult.isHasErrors()) { + throw new ConstraintViolationException(validationResult.getConstraintViolations()); + } + return identityProviderConfig; + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/identitysource/IdentitySourceConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/identitysource/IdentitySourceConverter.java new file mode 100644 index 00000000..63373488 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/identitysource/IdentitySourceConverter.java @@ -0,0 +1,280 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.identitysource; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import javax.validation.ConstraintViolationException; + +import org.apache.commons.lang3.StringUtils; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.data.domain.Page; +import org.springframework.data.querydsl.QPageRequest; +import org.springframework.util.CollectionUtils; + +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONWriter; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.querydsl.core.types.ExpressionUtils; +import com.querydsl.core.types.Predicate; + +import cn.topiam.employee.common.constants.CommonConstants; +import cn.topiam.employee.common.entity.identitysource.IdentitySourceEntity; +import cn.topiam.employee.common.entity.identitysource.QIdentitySourceEntity; +import cn.topiam.employee.common.enums.identityprovider.IdentitySourceProvider; +import cn.topiam.employee.console.pojo.query.identity.IdentitySourceListQuery; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceConfigGetResult; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceGetResult; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceListResult; +import cn.topiam.employee.console.pojo.save.identitysource.IdentitySourceConfigSaveParam; +import cn.topiam.employee.console.pojo.save.identitysource.IdentitySourceCreateParam; +import cn.topiam.employee.console.pojo.update.identity.IdentitySourceUpdateParam; +import cn.topiam.employee.core.context.ServerContextHelp; +import cn.topiam.employee.identitysource.core.IdentitySourceConfig; +import cn.topiam.employee.identitysource.dingtalk.DingTalkConfig; +import cn.topiam.employee.identitysource.feishu.FeiShuConfig; +import cn.topiam.employee.identitysource.wechatwork.WeChatWorkConfig; +import cn.topiam.employee.support.exception.TopIamException; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.repository.page.domain.QueryDslRequest; +import cn.topiam.employee.support.validation.ValidationHelp; + +/** + * 身份源转换器 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/13 21:37 + */ +@Mapper(componentModel = "spring") +public interface IdentitySourceConverter { + + ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + /** + * 身份源平台枚举转换器到身份源平台结果 + * + * @param values {@link IdentitySourceEntity} + * @return {@link List} + */ + default cn.topiam.employee.support.repository.page.domain.Page entityConverterToIdentitySourceListResult(Page values) { + + cn.topiam.employee.support.repository.page.domain.Page result = new cn.topiam.employee.support.repository.page.domain.Page<>(); + if (!CollectionUtils.isEmpty(values.getContent())) { + ArrayList list = new ArrayList<>(); + for (IdentitySourceEntity entity : values.getContent()) { + list.add(entityConverterToIdentitySourceListResult(entity)); + } + + //@formatter:off + result.setPagination(cn.topiam.employee.support.repository.page.domain.Page.Pagination.builder() + .total(values.getTotalElements()) + .totalPages(values.getTotalPages()) + .current(values.getPageable().getPageNumber() + 1) + .build()); + //@formatter:on + result.setList(list); + } + return result; + } + + /** + * 身份源平台枚举转换器到身份源平台结果 + * + * @param value {@link IdentitySourceEntity} + * @return {@link List} + */ + @Mapping(target = "desc", source = "provider.desc") + @Mapping(target = "provider", source = "provider.code") + @Mapping(target = "icon", ignore = true) + IdentitySourceListResult entityConverterToIdentitySourceListResult(IdentitySourceEntity value); + + /** + * 创建入参转换为实体类 + * + * @param param {@link IdentitySourceCreateParam} + * @return {@link IdentitySourceEntity} + */ + @Mapping(target = "code", expression = "java(org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric(32).toLowerCase())") + @Mapping(target = "configured", expression = "java(Boolean.FALSE)") + @Mapping(target = "enabled", expression = "java(Boolean.TRUE)") + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "basicConfig", expression = "java(\"{}\")") + @Mapping(target = "jobConfig", expression = "java(new cn.topiam.employee.common.entity.identitysource.config.JobConfig())") + @Mapping(target = "strategyConfig", expression = "java(new cn.topiam.employee.common.entity.identitysource.config.StrategyConfig())") + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "remark", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + IdentitySourceEntity createParamConverterToEntity(IdentitySourceCreateParam param); + + /** + * entity转换为详细信息结果 + * + * @param entity {@link IdentitySourceEntity} + * @return {@link IdentitySourceGetResult} + */ + default IdentitySourceGetResult entityConverterToIdentitySourceGetResult(IdentitySourceEntity entity) { + if (entity == null) { + return null; + } + IdentitySourceGetResult identitySourceGetResult = new IdentitySourceGetResult(); + if (entity.getId() != null) { + identitySourceGetResult.setId(String.valueOf(entity.getId())); + } + identitySourceGetResult.setName(entity.getName()); + identitySourceGetResult.setProvider(entity.getProvider()); + identitySourceGetResult.setEnabled(entity.getEnabled()); + identitySourceGetResult.setConfigured(entity.getConfigured()); + identitySourceGetResult.setRemark(entity.getRemark()); + return identitySourceGetResult; + } + + /** + * 保存入参转换为实体类 + * + * @param param {@link IdentitySourceCreateParam} + * @return {@link IdentitySourceEntity} + */ + @Mapping(target = "code", ignore = true) + @Mapping(target = "configured", ignore = true) + @Mapping(target = "strategyConfig", ignore = true) + @Mapping(target = "basicConfig", ignore = true) + @Mapping(target = "jobConfig", ignore = true) + @Mapping(target = "provider", ignore = true) + @Mapping(target = "enabled", ignore = true) + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + IdentitySourceEntity updateParamConverterToEntity(IdentitySourceUpdateParam param); + + /** + * 保存配置参数转entity + * + * @param param {@link IdentitySourceConfigSaveParam} + * @return {@link IdentitySourceEntity} + */ + @Mapping(target = "code", ignore = true) + @Mapping(target = "configured", expression = "java(Boolean.TRUE)") + @Mapping(target = "basicConfig", ignore = true) + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "remark", ignore = true) + @Mapping(target = "provider", ignore = true) + @Mapping(target = "name", ignore = true) + @Mapping(target = "enabled", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + IdentitySourceEntity saveConfigParamConverterToEntity(IdentitySourceConfigSaveParam param); + + /** + * 保存配置参数转entity + * + * @param param {@link IdentitySourceConfigSaveParam} + * @param provider {@link IdentitySourceProvider} + * @return {@link IdentitySourceEntity} + */ + default IdentitySourceEntity saveConfigParamConverterToEntity(IdentitySourceConfigSaveParam param, + IdentitySourceProvider provider) { + ValidationHelp.ValidationResult validationResult; + IdentitySourceConfig clientConfig = null; + //钉钉 + if (Objects.equals(provider, IdentitySourceProvider.DINGTALK)) { + clientConfig = param.getBasicConfig().to(DingTalkConfig.class); + } + //企业微信 + if (Objects.equals(provider, IdentitySourceProvider.WECHAT_WORK)) { + clientConfig = param.getBasicConfig().to(WeChatWorkConfig.class); + } + //飞书 + if (Objects.equals(provider, IdentitySourceProvider.FEISHU)) { + clientConfig = param.getBasicConfig().to(FeiShuConfig.class); + } + //放置参数,并验证参数 + if (!Objects.nonNull(clientConfig)) { + throw new NullPointerException("提供商配置不能为空!"); + } + validationResult = ValidationHelp.validateEntity(clientConfig); + //处理异常 + if (validationResult.isHasErrors()) { + throw new ConstraintViolationException(validationResult.getConstraintViolations()); + } + //封装数据 + IdentitySourceEntity source = saveConfigParamConverterToEntity(param); + source.setBasicConfig( + JSONObject.toJSONString(clientConfig, JSONWriter.Feature.WriteClassName)); + return source; + } + + /** + * 查询身份源列表参数转 Request + * + * @param query {@link IdentitySourceListQuery} + * @param pageModel {@link PageModel} + * @return {@link QueryDslRequest } + */ + default QueryDslRequest queryIdentitySourceListParamConvertToPredicate(IdentitySourceListQuery query, + PageModel pageModel) { + QueryDslRequest request = new QueryDslRequest(); + QIdentitySourceEntity queryEntity = QIdentitySourceEntity.identitySourceEntity; + Predicate predicate = queryEntity.isNotNull(); + //查询条件 + //@formatter:off + predicate = StringUtils.isBlank(query.getName()) ? predicate : ExpressionUtils.and(predicate, queryEntity.name.like("%" + query.getName() + "%")); + //@formatter:on + request.setPredicate(predicate); + //分页条件 + //@formatter:off + request.setPageRequest(QPageRequest.of(pageModel.getCurrent(), pageModel.getPageSize())); + return request; + } + + /** + * entity转换为配置结果 + * + * @param entity {@link IdentitySourceEntity} + * @return {@link IdentitySourceConfigGetResult} + */ + default IdentitySourceConfigGetResult entityConverterToIdentitySourceConfigGetResult(IdentitySourceEntity entity){ + if (entity == null) { + return null; + } + IdentitySourceConfigGetResult identitySourceResult = new IdentitySourceConfigGetResult(); + if (entity.getId() != null) { + identitySourceResult.setId(String.valueOf(entity.getId())); + } + identitySourceResult.setConfigured(entity.getConfigured()); + identitySourceResult.setJobConfig(entity.getJobConfig()); + identitySourceResult.setStrategyConfig(entity.getStrategyConfig()); + try { + JSONObject value = OBJECT_MAPPER.readValue(entity.getBasicConfig(), JSONObject.class); + value.remove(CommonConstants.TYPE); + //@formatter:off + value.put(CommonConstants.CALLBACK_URL, ServerContextHelp.getSynchronizerPublicBaseUrl() + "/api/synchronizer/event_receive/" + entity.getCode()); + //@formatter:on + identitySourceResult.setBasicConfig(value); + } catch (Exception e) { + throw new TopIamException(e.getMessage()); + } + return identitySourceResult; + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/identitysource/IdentitySourceEventRecordConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/identitysource/IdentitySourceEventRecordConverter.java new file mode 100644 index 00000000..dd31defb --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/identitysource/IdentitySourceEventRecordConverter.java @@ -0,0 +1,103 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.identitysource; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; +import org.mapstruct.Mapper; +import org.springframework.util.CollectionUtils; + +import com.querydsl.core.types.ExpressionUtils; +import com.querydsl.core.types.Predicate; + +import cn.topiam.employee.common.entity.account.UserGroupEntity; +import cn.topiam.employee.common.entity.identitysource.IdentitySourceEventRecordEntity; +import cn.topiam.employee.common.entity.identitysource.QIdentitySourceEventRecordEntity; +import cn.topiam.employee.console.pojo.query.identity.IdentitySourceEventRecordListQuery; +import cn.topiam.employee.console.pojo.result.account.UserGroupListResult; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceEventRecordListResult; +import cn.topiam.employee.support.context.ApplicationContextHelp; +import cn.topiam.employee.support.repository.page.domain.Page; + +/** + * 身份源事件记录转换器 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/13 21:37 + */ +@Mapper(componentModel = "spring") +public interface IdentitySourceEventRecordConverter { + + /** + * 身份源事件记录列表参数转换为 Querydsl Predicate + * + * @param query {@link IdentitySourceEventRecordListQuery} query + * @return {@link Predicate} + */ + default Predicate queryIdentitySourceEventRecordListQueryConvertToPredicate(IdentitySourceEventRecordListQuery query) { + QIdentitySourceEventRecordEntity queryEntity = QIdentitySourceEventRecordEntity.identitySourceEventRecordEntity; + Predicate predicate = queryEntity.isNotNull(); + //查询条件 + //@formatter:off + predicate = StringUtils.isBlank(query.getIdentitySourceId()) ? predicate : ExpressionUtils.and(predicate, queryEntity.identitySourceId.eq(Long.valueOf(query.getIdentitySourceId()))); + predicate = Objects.isNull(query.getActionType()) ? predicate : ExpressionUtils.and(predicate, queryEntity.actionType.eq(query.getActionType())); + predicate = Objects.isNull(query.getObjectType()) ? predicate : ExpressionUtils.and(predicate, queryEntity.objectType.eq(query.getObjectType())); + predicate = Objects.isNull(query.getStatus()) ? predicate : ExpressionUtils.and(predicate, queryEntity.status.eq(query.getStatus())); + //@formatter:on + return predicate; + } + + /** + * 身份源事件记录实体转换为身份源事件记录分页结果 + * + * @param page {@link Page} + * @return {@link Page} + */ + default Page entityConvertToIdentitySourceSyncRecordListResult(org.springframework.data.domain.Page page) { + Page result = new Page<>(); + if (!CollectionUtils.isEmpty(page.getContent())) { + List list = new ArrayList<>(); + for (IdentitySourceEventRecordEntity entity : page.getContent()) { + IdentitySourceEventRecordConverter bean = ApplicationContextHelp + .getBean(IdentitySourceEventRecordConverter.class); + list.add(bean.entityConvertToIdentitySourceSyncRecordListResult(entity)); + } + + //@formatter:off + result.setPagination(Page.Pagination.builder() + .total(page.getTotalElements()) + .totalPages(page.getTotalPages()) + .current(page.getPageable().getPageNumber() + 1) + .build()); + //@formatter:on + result.setList(list); + } + return result; + } + + /** + * 身份源事件记录转换为身份源事件记录分页结果 + * + * @param entity {@link UserGroupEntity} + * @return {@link UserGroupListResult} + */ + IdentitySourceEventRecordListResult entityConvertToIdentitySourceSyncRecordListResult(IdentitySourceEventRecordEntity entity); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/identitysource/IdentitySourceSyncConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/identitysource/IdentitySourceSyncConverter.java new file mode 100644 index 00000000..7649e218 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/identitysource/IdentitySourceSyncConverter.java @@ -0,0 +1,203 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.identitysource; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.util.CollectionUtils; + +import com.querydsl.core.types.ExpressionUtils; +import com.querydsl.core.types.Predicate; + +import cn.topiam.employee.common.entity.account.UserGroupEntity; +import cn.topiam.employee.common.entity.identitysource.IdentitySourceSyncHistoryEntity; +import cn.topiam.employee.common.entity.identitysource.IdentitySourceSyncRecordEntity; +import cn.topiam.employee.common.entity.identitysource.QIdentitySourceSyncHistoryEntity; +import cn.topiam.employee.common.entity.identitysource.QIdentitySourceSyncRecordEntity; +import cn.topiam.employee.console.pojo.query.identity.IdentitySourceSyncHistoryListQuery; +import cn.topiam.employee.console.pojo.query.identity.IdentitySourceSyncRecordListQuery; +import cn.topiam.employee.console.pojo.result.account.UserGroupListResult; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceSyncHistoryListResult; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceSyncRecordListResult; +import cn.topiam.employee.support.repository.page.domain.Page; + +/** + * 身份源转换器 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/13 21:37 + */ +@Mapper(componentModel = "spring") +public interface IdentitySourceSyncConverter { + + /** + * 身份源同步列表参数转换为 Querydsl Predicate + * + * @param query {@link IdentitySourceSyncHistoryListQuery} query + * @return {@link Predicate} + */ + default Predicate queryIdentitySourceSyncHistoryListQueryConvertToPredicate(IdentitySourceSyncHistoryListQuery query) { + QIdentitySourceSyncHistoryEntity queryEntity = QIdentitySourceSyncHistoryEntity.identitySourceSyncHistoryEntity; + Predicate predicate = queryEntity.isNotNull(); + //查询条件 + //@formatter:off + predicate = StringUtils.isBlank(query.getIdentitySourceId()) ? predicate : ExpressionUtils.and(predicate, queryEntity.identitySourceId.eq(Long.valueOf(query.getIdentitySourceId()))); + predicate = Objects.isNull(query.getObjectType()) ? predicate : ExpressionUtils.and(predicate, queryEntity.objectType.eq(query.getObjectType())); + predicate = Objects.isNull(query.getTriggerType()) ? predicate : ExpressionUtils.and(predicate, queryEntity.triggerType.eq(query.getTriggerType())); + predicate = Objects.isNull(query.getStatus()) ? predicate : ExpressionUtils.and(predicate, queryEntity.status.eq(query.getStatus())); + //@formatter:on + return predicate; + } + + /** + * 身份源同步实体转换为身份源同步分页结果 + * + * @param page {@link Page} + * @return {@link Page} + */ + default Page entityConvertToIdentitySourceSyncHistoryListResult(org.springframework.data.domain.Page page) { + Page result = new Page<>(); + if (!CollectionUtils.isEmpty(page.getContent())) { + List list = new ArrayList<>(); + for (IdentitySourceSyncHistoryEntity entity : page.getContent()) { + list.add(entityConvertToIdentitySourceSyncHistoryListResult(entity)); + } + //@formatter:off + result.setPagination(Page.Pagination.builder() + .total(page.getTotalElements()) + .totalPages(page.getTotalPages()) + .current(page.getPageable().getPageNumber() + 1) + .build()); + //@formatter:on + result.setList(list); + } + return result; + } + + /** + * 身份源同步转换为身份源同步分页结果 + * + * @param entity {@link UserGroupEntity} + * @return {@link UserGroupListResult} + */ + default IdentitySourceSyncHistoryListResult entityConvertToIdentitySourceSyncHistoryListResult(IdentitySourceSyncHistoryEntity entity) { + if (entity == null) { + return null; + } + + IdentitySourceSyncHistoryListResult identitySourceSyncHistoryListResult = new IdentitySourceSyncHistoryListResult(); + + if (entity.getId() != null) { + identitySourceSyncHistoryListResult.setId(String.valueOf(entity.getId())); + } + identitySourceSyncHistoryListResult.setBatch(entity.getBatch()); + if (entity.getIdentitySourceId() != null) { + identitySourceSyncHistoryListResult + .setIdentitySourceId(String.valueOf(entity.getIdentitySourceId())); + } + if (entity.getCreatedCount() != null) { + identitySourceSyncHistoryListResult + .setCreatedCount(String.valueOf(entity.getCreatedCount())); + } + if (entity.getUpdatedCount() != null) { + identitySourceSyncHistoryListResult + .setUpdatedCount(String.valueOf(entity.getUpdatedCount())); + } + if (entity.getSkippedCount() != null) { + identitySourceSyncHistoryListResult + .setSkippedCount(String.valueOf(entity.getSkippedCount())); + } + if (entity.getDeletedCount() != null) { + identitySourceSyncHistoryListResult + .setDeletedCount(String.valueOf(entity.getDeletedCount())); + } + identitySourceSyncHistoryListResult.setStartTime(entity.getStartTime()); + identitySourceSyncHistoryListResult.setEndTime(entity.getEndTime()); + identitySourceSyncHistoryListResult.setObjectType(entity.getObjectType()); + identitySourceSyncHistoryListResult.setTriggerType(entity.getTriggerType()); + if (entity.getStatus() != null) { + identitySourceSyncHistoryListResult.setStatus(entity.getStatus().getCode()); + } + if (entity.getEndTime() != null) { + identitySourceSyncHistoryListResult.setSpendTime(String + .valueOf( + entity.getEndTime().toInstant(java.time.ZoneOffset.of("+8")).getEpochSecond() + - entity.getStartTime().toInstant(java.time.ZoneOffset.of("+8")) + .getEpochSecond())); + } + return identitySourceSyncHistoryListResult; + } + + /** + * 查询身份源同步详情列表参数转换为 Querydsl Predicate + * + * @param query {@link IdentitySourceSyncRecordListQuery} query + * @return {@link Predicate} + */ + default Predicate queryIdentitySourceSyncRecordListQueryConvertToPredicate(IdentitySourceSyncRecordListQuery query) { + QIdentitySourceSyncRecordEntity entity = QIdentitySourceSyncRecordEntity.identitySourceSyncRecordEntity; + Predicate predicate = entity.isNotNull(); + //查询条件 + //@formatter:off + predicate = StringUtils.isBlank(query.getSyncHistoryId()) ? predicate : ExpressionUtils.and(predicate, entity.syncHistoryId.eq(Long.valueOf(query.getSyncHistoryId()))); + predicate = Objects.isNull(query.getObjectType()) ? predicate : ExpressionUtils.and(predicate, entity.objectType.eq(query.getObjectType())); + predicate = Objects.isNull(query.getActionType()) ? predicate : ExpressionUtils.and(predicate, entity.actionType.eq(query.getActionType())); + predicate = Objects.isNull(query.getStatus()) ? predicate : ExpressionUtils.and(predicate, entity.status.eq(query.getStatus())); + //@formatter:on + return predicate; + } + + /** + * 身份源同步详情转换为身份源详情分页结果 + * + * @param page {@link Page} + * @return {@link Page} + */ + default Page entityConvertToIdentitySourceSyncRecordListResult(org.springframework.data.domain.Page page) { + Page result = new Page<>(); + if (!CollectionUtils.isEmpty(page.getContent())) { + List list = new ArrayList<>(); + for (IdentitySourceSyncRecordEntity entity : page.getContent()) { + list.add(entityConvertToIdentitySourceSyncRecordListResult(entity)); + } + //@formatter:off + result.setPagination(Page.Pagination.builder() + .total(page.getTotalElements()) + .totalPages(page.getTotalPages()) + .current(page.getPageable().getPageNumber() + 1) + .build()); + //@formatter:on + result.setList(list); + } + return result; + } + + /** + * 身份源同步详情实体转换为身份源同步详情分页结果 + * + * @param page {@link UserGroupEntity} + * @return {@link UserGroupListResult} + */ + @Mapping(target = "status", source = "status.code") + IdentitySourceSyncRecordListResult entityConvertToIdentitySourceSyncRecordListResult(IdentitySourceSyncRecordEntity page); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/package-info.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/package-info.java new file mode 100644 index 00000000..ad486b61 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/package-info.java @@ -0,0 +1,22 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/8/16 20:12 + */ +package cn.topiam.employee.console.converter; \ No newline at end of file diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/AdministratorConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/AdministratorConverter.java new file mode 100644 index 00000000..9a547de8 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/AdministratorConverter.java @@ -0,0 +1,150 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.setting; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.util.CollectionUtils; + +import com.querydsl.core.types.ExpressionUtils; +import com.querydsl.core.types.Predicate; + +import cn.topiam.employee.common.entity.account.query.UserListQuery; +import cn.topiam.employee.common.entity.setting.AdministratorEntity; +import cn.topiam.employee.common.entity.setting.QAdministratorEntity; +import cn.topiam.employee.console.pojo.query.setting.AdministratorListQuery; +import cn.topiam.employee.console.pojo.result.setting.AdministratorListResult; +import cn.topiam.employee.console.pojo.result.setting.AdministratorResult; +import cn.topiam.employee.console.pojo.save.setting.AdministratorCreateParam; +import cn.topiam.employee.console.pojo.update.setting.AdministratorUpdateParam; +import cn.topiam.employee.support.repository.page.domain.Page; + +/** + * 管理员映射 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/14 22:45 + */ +@Mapper(componentModel = "spring") +public interface AdministratorConverter { + + /** + * 管理员实体转换为管理员分页结果 + * + * @param page {@link Page} + * @return {@link Page} + */ + default Page entityConvertToAdministratorPaginationResult(org.springframework.data.domain.Page page) { + Page result = new Page<>(); + if (!CollectionUtils.isEmpty(page.getContent())) { + List list = new ArrayList<>(); + for (AdministratorEntity user : page.getContent()) { + list.add(entityConvertToAdministratorPaginationResult(user)); + } + //@formatter:off + result.setPagination(Page.Pagination.builder() + .total(page.getTotalElements()) + .totalPages(page.getTotalPages()) + .current(page.getPageable().getPageNumber() + 1) + .build()); + //@formatter:on + result.setList(list); + } + return result; + } + + /** + * 管理员实体转换为管理员分页结果 + * + * @param page {@link AdministratorEntity} + * @return {@link AdministratorListResult} + */ + @Mapping(target = "status", source = "status.code") + @Mapping(target = "emailVerified", source = "emailVerified", defaultValue = "false") + @Mapping(target = "authTotal", source = "authTotal", defaultValue = "0L") + AdministratorListResult entityConvertToAdministratorPaginationResult(AdministratorEntity page); + + /** + * 管理员创建参数转换为管理员实体 + * + * @param param {@link AdministratorCreateParam} + * @return {@link AdministratorEntity} + */ + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "status", expression = "java(cn.topiam.employee.common.enums.UserStatus.ENABLE)") + @Mapping(target = "lastAuthTime", ignore = true) + @Mapping(target = "lastAuthIp", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "expand", ignore = true) + @Mapping(target = "emailVerified", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + @Mapping(target = "authTotal", ignore = true) + AdministratorEntity administratorCreateParamConvertToEntity(AdministratorCreateParam param); + + /** + * 管理员更新参数转换为管理员实体类 + * + * @param param {@link AdministratorUpdateParam} 更新参数 + * @return {@link AdministratorEntity} 管理员实体 + */ + @Mapping(target = "username", ignore = true) + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "status", ignore = true) + @Mapping(target = "password", ignore = true) + @Mapping(target = "lastAuthTime", ignore = true) + @Mapping(target = "lastAuthIp", ignore = true) + @Mapping(target = "expand", ignore = true) + @Mapping(target = "emailVerified", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + @Mapping(target = "authTotal", ignore = true) + AdministratorEntity administratorUpdateParamConvertToEntity(AdministratorUpdateParam param); + + /** + * 实体转为管理员详情返回 + * + * @param user {@link AdministratorEntity} + * @return {@link AdministratorResult} 管理员详情 + */ + @Mapping(target = "status", source = "status.code") + AdministratorResult entityConvertToAdministratorDetailsResult(AdministratorEntity user); + + /** + * 查询管理员列表参数转换为 Querydsl Predicate + * + * @param query {@link UserListQuery} query + * @return {@link Predicate} + */ + default Predicate queryAdministratorListParamConvertToPredicate(AdministratorListQuery query) { + QAdministratorEntity user = QAdministratorEntity.administratorEntity; + Predicate predicate = user.isNotNull(); + //查询条件 + //@formatter:off + predicate = StringUtils.isBlank(query.getUsername()) ? predicate : ExpressionUtils.and(predicate, user.username.eq(query.getUsername())); + //@formatter:on + return predicate; + } + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/GeoLocationSettingConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/GeoLocationSettingConverter.java new file mode 100644 index 00000000..cf289f3a --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/GeoLocationSettingConverter.java @@ -0,0 +1,119 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.setting; + +import java.util.Objects; + +import javax.validation.ValidationException; + +import org.mapstruct.Mapper; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import cn.topiam.employee.common.entity.setting.SettingEntity; +import cn.topiam.employee.common.geo.GeoLocationProviderConfig; +import cn.topiam.employee.common.geo.maxmind.MaxmindProviderConfig; +import cn.topiam.employee.common.geo.maxmind.enums.GeoLocationProvider; +import cn.topiam.employee.console.pojo.result.setting.EmailProviderConfigResult; +import cn.topiam.employee.console.pojo.result.setting.GeoIpProviderResult; +import cn.topiam.employee.console.pojo.save.setting.GeoIpProviderSaveParam; +import cn.topiam.employee.console.pojo.save.setting.MailProviderSaveParam; +import cn.topiam.employee.support.validation.ValidationHelp; +import static cn.topiam.employee.core.setting.constant.GeoIpProviderConstants.IPADDRESS_SETTING_NAME; + +/** + * 地理位置设置转换器 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/10/1 23:18 + */ +@Mapper(componentModel = "spring") +public interface GeoLocationSettingConverter { + /** + * 地理位置配置转实体类 + * + * @param param {@link MailProviderSaveParam} + * @return {@link SettingEntity} + */ + default SettingEntity geoLocationProviderConfigToEntity(GeoIpProviderSaveParam param) { + ObjectMapper objectMapper = new ObjectMapper(); + // 指定序列化输入的类型 + objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), + ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); + try { + SettingEntity entity = new SettingEntity(); + entity.setName(IPADDRESS_SETTING_NAME); + String desc = null; + ValidationHelp.ValidationResult validationResult = null; + //@formatter:off + //根据提供商封装参数 + if (GeoLocationProvider.MAXMIND.equals(param.getProvider())) { + desc = GeoLocationProvider.MAXMIND.getName(); + MaxmindProviderConfig maxmindProviderConfig = param.getConfig().to(MaxmindProviderConfig.class); + validationResult = ValidationHelp.validateEntity(maxmindProviderConfig); + entity.setValue(objectMapper.writeValueAsString(new GeoLocationProviderConfig(param.getProvider(), maxmindProviderConfig))); + } + // 验证 + if (Objects.requireNonNull(validationResult).isHasErrors()) { + throw new ValidationException(validationResult.getMessage()); + } + entity.setDesc(desc); + //@formatter:no + return entity; + }catch (JsonProcessingException e){ + throw new RuntimeException(e); + } + } + + /** + * 实体转地理位置提供商配置 + * + * @param entity {@link SettingEntity} + * @return {@link EmailProviderConfigResult} + */ + default GeoIpProviderResult entityToGeoLocationProviderConfig(SettingEntity entity) { + //没有数据,默认未启用 + if (Objects.isNull(entity)) { + return null; + } + try { + String value = entity.getValue(); + ObjectMapper objectMapper = new ObjectMapper(); + // 指定序列化输入的类型 + objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), + ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); + // 根据提供商序列化 + GeoLocationProviderConfig setting = objectMapper.readValue(value, GeoLocationProviderConfig.class); + if (GeoLocationProvider.MAXMIND.equals(setting.getProvider())) { + MaxmindProviderConfig config = (MaxmindProviderConfig) setting.getConfig(); + //@formatter:off + return GeoIpProviderResult.builder() + .provider(setting.getProvider()) + .config(config) + .enabled(true) + .build(); + } + //@formatter:on + return null; + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/MailTemplateConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/MailTemplateConverter.java new file mode 100644 index 00000000..c7c44f2e --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/MailTemplateConverter.java @@ -0,0 +1,118 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.setting; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import cn.topiam.employee.common.entity.setting.MailTemplateEntity; +import cn.topiam.employee.common.enums.MailType; +import cn.topiam.employee.console.pojo.result.setting.EmailTemplateListResult; +import cn.topiam.employee.console.pojo.result.setting.EmailTemplateResult; +import cn.topiam.employee.console.pojo.save.setting.EmailCustomTemplateSaveParam; +import static org.springframework.web.util.HtmlUtils.htmlUnescape; + +import static cn.topiam.employee.core.message.mail.MailUtils.readEmailContent; + +/** + * 消息服务数据映射 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/17 23:12 + */ +@Mapper(componentModel = "spring") +public interface MailTemplateConverter { + /** + * 实体转为电子邮件模板配置返回 + * + * @param template {@link MailTemplateEntity} + * @return {@link EmailTemplateResult} + */ + @Mapping(target = "custom", expression = "java(java.lang.Boolean.TRUE)") + @Mapping(target = "desc", expression = "java(template.getType().getDesc())") + @Mapping(target = "name", expression = "java(template.getType().getName())") + @Mapping(target = "content", expression = "java(org.springframework.web.util.HtmlUtils.htmlUnescape(template.getContent()))") + EmailTemplateResult entityConvertToEmailTemplateDetailResult(MailTemplateEntity template); + + /** + * 电子邮件模板配置更新参数转换为 entity + * + * @param param {@link EmailCustomTemplateSaveParam} + * @return {@link MailTemplateEntity} + */ + @Mapping(target = "updateTime", ignore = true) + @Mapping(target = "updateBy", ignore = true) + @Mapping(target = "type", ignore = true) + @Mapping(target = "remark", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "createTime", ignore = true) + @Mapping(target = "createBy", ignore = true) + @Mapping(target = "content", expression = "java(org.springframework.web.util.HtmlUtils.htmlEscape(param.getContent()))") + MailTemplateEntity emailTemplateConfigSaveParamConvertToEntity(EmailCustomTemplateSaveParam param); + + /** + * 枚举列表转邮件模板类型列表 + * + * @param values {@link List>} + * @param entities {@link List>} + * @return {@link List>} + */ + default List mailTemplateTypeConvertToEmailTemplateListResult(List values, + List entities) { + List results = new ArrayList<>(); + //处理枚举 + for (MailType value : values) { + EmailTemplateListResult cipher = new EmailTemplateListResult(); + cipher.setCode(value.getCode()); + cipher.setName(value.getName()); + cipher.setDescription(value.getDesc()); + cipher.setContent(value.getContent()); + cipher.setContent(htmlUnescape(readEmailContent(value.getContent()))); + results.add(cipher); + } + // 数据库有,为自定义 + for (EmailTemplateListResult result : results) { + for (MailTemplateEntity entity : entities) { + if (result.getCode().equals(entity.getType().getCode())) { + result.setCustom(true); + result.setContent(htmlUnescape(entity.getContent())); + } + } + } + return results; + } + + /** + * 邮件类型枚举转为邮件模板详情返回 + * + * @param templateType {@link MailType} + * @return {@link EmailTemplateResult} + */ + default EmailTemplateResult mailTemplateTypeConvertToEmailTemplateDetailResult(MailType templateType) { + EmailTemplateResult result = new EmailTemplateResult(); + result.setContent(htmlUnescape(readEmailContent(templateType.getContent()))); + result.setDesc(templateType.getDesc()); + result.setSender(templateType.getSender()); + result.setSubject(templateType.getSubject()); + result.setCustom(false); + return result; + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/MessageSettingConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/MessageSettingConverter.java new file mode 100644 index 00000000..698fd8de --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/MessageSettingConverter.java @@ -0,0 +1,237 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.setting; + +import java.util.Objects; + +import javax.validation.ValidationException; + +import org.mapstruct.Mapper; + +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONWriter; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import cn.topiam.employee.common.entity.setting.SettingEntity; +import cn.topiam.employee.common.entity.setting.config.SmsConfig; +import cn.topiam.employee.common.enums.MessageNoticeChannel; +import cn.topiam.employee.common.message.enums.MailProvider; +import cn.topiam.employee.common.message.enums.MailSafetyType; +import cn.topiam.employee.common.message.enums.SmsProvider; +import cn.topiam.employee.common.message.mail.MailProviderConfig; +import cn.topiam.employee.common.message.sms.SmsProviderConfig; +import cn.topiam.employee.common.message.sms.aliyun.AliyunSmsProviderConfig; +import cn.topiam.employee.common.message.sms.qiniu.QiNiuSmsProviderConfig; +import cn.topiam.employee.common.message.sms.tencent.TencentSmsProviderConfig; +import cn.topiam.employee.console.pojo.result.setting.EmailProviderConfigResult; +import cn.topiam.employee.console.pojo.save.setting.MailProviderSaveParam; +import cn.topiam.employee.console.pojo.save.setting.SmsProviderSaveParam; +import cn.topiam.employee.console.pojo.setting.SmsProviderConfigResult; +import cn.topiam.employee.support.util.AesUtils; +import cn.topiam.employee.support.validation.ValidationHelp; +import static cn.topiam.employee.core.context.SettingContextHelp.getSmsProviderConfig; +import static cn.topiam.employee.core.setting.constant.MessageSettingConstants.MESSAGE_PROVIDER_EMAIL; +import static cn.topiam.employee.core.setting.constant.MessageSettingConstants.MESSAGE_SMS_PROVIDER; + +/** + * 消息设置转换器 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/10/1 23:18 + */ +@Mapper(componentModel = "spring") +public interface MessageSettingConverter { + /** + * 邮件提供商配置转实体类 + * + * @param param {@link MailProviderSaveParam} + * @return {@link SettingEntity} + */ + default SettingEntity mailProviderConfigToEntity(MailProviderSaveParam param) { + SettingEntity entity = new SettingEntity(); + entity.setName(MESSAGE_PROVIDER_EMAIL); + String desc = MessageNoticeChannel.MAIL.getDesc(); + ValidationHelp.ValidationResult validationResult = null; + //@formatter:off + MailProviderConfig.MailProviderConfigBuilder builder = + MailProviderConfig.builder() + .username(param.getUsername()) + .secret(AesUtils.encrypt(param.getSecret())); + //根据提供商封装参数 + if (MailProvider.CUSTOMIZE.equals(param.getProvider())) { + desc = desc + MailProvider.CUSTOMIZE.getName(); + builder + .provider(MailProvider.CUSTOMIZE) + .smtpUrl(param.getSmtpUrl()) + .port(param.getPort()) + .safetyType(param.getSafetyType()); + validationResult = ValidationHelp.validateEntity(builder.build()); + } + //阿里云 + if (MailProvider.ALIYUN.equals(param.getProvider())) { + desc = desc + MailProvider.ALIYUN.getName(); + builder + .provider(MailProvider.ALIYUN) + .smtpUrl(MailProvider.ALIYUN.getSmtpUrl()) + .port(MailProvider.ALIYUN.getSslPort()) + .safetyType(MailSafetyType.SSL); + validationResult = ValidationHelp.validateEntity(builder.build()); + } + //腾讯 + if (MailProvider.TENCENT.equals(param.getProvider())) { + desc = desc + MailProvider.TENCENT.getName(); + builder + .provider(MailProvider.TENCENT) + .smtpUrl(MailProvider.TENCENT.getSmtpUrl()) + .port(MailProvider.TENCENT.getSslPort()) + .safetyType(MailSafetyType.SSL); + validationResult = ValidationHelp.validateEntity(builder.build()); + } + //网易 + if (MailProvider.NETEASE.equals(param.getProvider())) { + desc = desc + MailProvider.NETEASE.getName(); + builder + .provider(MailProvider.NETEASE) + .smtpUrl(MailProvider.NETEASE.getSmtpUrl()) + .port(MailProvider.NETEASE.getSslPort()) + .safetyType(MailSafetyType.SSL); + validationResult = ValidationHelp.validateEntity(builder.build()); + } + // 验证 + if (Objects.requireNonNull(validationResult).isHasErrors()) { + throw new ValidationException(validationResult.getMessage()); + } + entity.setValue(JSONObject.toJSONString(builder.build(), JSONWriter.Feature.WriteClassName)); + entity.setDesc(desc); + //@formatter:no + return entity; + } + + /** + * 短信提供商配置转实体类 + * + * @param param {@link SmsProviderSaveParam} + * @return {@link SettingEntity} + */ + default SettingEntity smsProviderConfigToEntity(SmsProviderSaveParam param) { + ValidationHelp.ValidationResult validationResult = null; + String desc = MessageNoticeChannel.SMS.getDesc(); + SmsProviderConfig providerConfig = new SmsProviderConfig(); + ObjectMapper objectMapper = new ObjectMapper(); + try { + // 七牛云 + if (SmsProvider.QINIU.equals(param.getProvider())) { + QiNiuSmsProviderConfig smsConfig = objectMapper.readValue(param.getConfig().toJSONString(), QiNiuSmsProviderConfig.class); + validationResult = ValidationHelp.validateEntity(smsConfig); + smsConfig.setSecretKey(AesUtils.encrypt(smsConfig.getSecretKey())); + providerConfig = smsConfig; + desc = desc + SmsProvider.QINIU.getDesc(); + } + // 阿里云 + else if (SmsProvider.ALIYUN.equals(param.getProvider())) { + AliyunSmsProviderConfig smsConfig = objectMapper.readValue(param.getConfig().toJSONString(), AliyunSmsProviderConfig.class); + validationResult = ValidationHelp.validateEntity(smsConfig); + smsConfig.setAccessKeySecret(AesUtils.encrypt(smsConfig.getAccessKeySecret())); + providerConfig = smsConfig; + desc = desc + SmsProvider.ALIYUN.getDesc(); + } + // 腾讯云 + else if (SmsProvider.TENCENT.equals(param.getProvider())) { + TencentSmsProviderConfig smsConfig = objectMapper.readValue(param.getConfig().toJSONString(), TencentSmsProviderConfig.class); + validationResult = ValidationHelp.validateEntity(smsConfig); + smsConfig.setSecretKey(AesUtils.encrypt(smsConfig.getSecretKey())); + providerConfig = smsConfig; + desc = desc + SmsProvider.TENCENT.getDesc(); + } + //判断并处理参数验证异常 + boolean hasErrors = Objects.requireNonNull(validationResult).isHasErrors(); + if (hasErrors) { + throw new ValidationException(validationResult.getMessage()); + } + //封装 + SmsConfig smsConfig=new SmsConfig(); + smsConfig.setConfig(providerConfig); + smsConfig.setProvider(param.getProvider()); + smsConfig.setLanguage(param.getLanguage()); + smsConfig.setTemplates(param.getTemplates()); + //保存 + SettingEntity entity = new SettingEntity(); + entity.setName(MESSAGE_SMS_PROVIDER); + // 指定序列化输入的类型(这里不要移动到上面,只有序列化时才使用) + String value = objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), + ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY).writeValueAsString(smsConfig); + entity.setValue(value); + entity.setDesc(desc); + return entity; + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + /** + * 实体转邮件提供商配置 + * + * @param entity {@link SettingEntity} + * @return {@link EmailProviderConfigResult} + */ + default EmailProviderConfigResult entityToMailProviderConfig(SettingEntity entity) { + //没有数据,默认未启用 + if (Objects.isNull(entity)) { + return EmailProviderConfigResult.builder().enabled(false).build(); + } + String config = entity.getValue(); + // 根据提供商序列化 + MailProviderConfig setting = JSONObject.parseObject(config, MailProviderConfig.class); + //@formatter:off + return EmailProviderConfigResult.builder() + .provider(setting.getProvider()) + .port(setting.getPort()) + .safetyType(setting.getSafetyType()) + .username(setting.getUsername()) + .secret(AesUtils.decrypt(setting.getSecret())) + .smtpUrl(setting.getSmtpUrl()) + .enabled(true) + .build(); + //@formatter:on + } + + /** + * 实体转短信提供商配置 + * + * @param entity {@link SettingEntity} + * @return {@link SmsProviderConfigResult} + */ + default SmsProviderConfigResult entityToSmsProviderConfig(SettingEntity entity) { + if (Objects.isNull(entity)) { + return SmsProviderConfigResult.builder().enabled(false).build(); + } + SmsConfig config = getSmsProviderConfig(); + //@formatter:off + SmsProviderConfigResult.SmsProviderConfigResultBuilder builder = SmsProviderConfigResult.builder() + .enabled(true) + .language(config.getLanguage().getLocale()) + .templates(config.getTemplates()) + .provider(config.getProvider()) + .config(config.getConfig()); + //@formatter:on + return builder.build(); + } + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/PasswordPolicyConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/PasswordPolicyConverter.java new file mode 100644 index 00000000..a81a1ade --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/PasswordPolicyConverter.java @@ -0,0 +1,219 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.setting; + +import java.util.*; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.mapstruct.Mapper; + +import cn.topiam.employee.common.entity.setting.SettingEntity; +import cn.topiam.employee.console.pojo.result.setting.PasswordPolicyConfigResult; +import cn.topiam.employee.console.pojo.save.setting.PasswordPolicySaveParam; +import cn.topiam.employee.core.security.password.enums.PasswordComplexityRule; +import static cn.topiam.employee.core.setting.constant.PasswordPolicySettingConstants.*; + +/** + * 密码规则数据转换 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/20 21:12 + */ +@Mapper(componentModel = "spring") +public interface PasswordPolicyConverter { + + /** + * 密码规则更新参数、转为实体类 + * + * @param param {@link PasswordPolicySaveParam} + * @return {@link List} + */ + default List passwordPolicySaveParamConvertToEntity(PasswordPolicySaveParam param) { + List list = new ArrayList<>(); + //最大长度 + if (ObjectUtils.isNotEmpty(param.getPasswordBiggestLength()) && !StringUtils.equals( + PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_BIGGEST_LENGTH), + Integer.toString(param.getPasswordBiggestLength()))) { + list.add(new SettingEntity().setName(PASSWORD_POLICY_BIGGEST_LENGTH) + .setValue(Integer.toString(param.getPasswordBiggestLength()))); + } + //最小长度 + if (ObjectUtils.isNotEmpty(param.getPasswordLeastLength()) && !StringUtils.equals( + PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_LEAST_LENGTH), + Integer.toString(param.getPasswordLeastLength()))) { + list.add(new SettingEntity().setName(PASSWORD_POLICY_LEAST_LENGTH) + .setValue(String.valueOf(param.getPasswordLeastLength()))); + } + //密码复杂度 + if (ObjectUtils.isNotEmpty(param.getPasswordComplexity()) + && !StringUtils.equals(PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_COMPLEXITY), + param.getPasswordComplexity().getCode())) { + list.add(new SettingEntity().setName(PASSWORD_POLICY_COMPLEXITY) + .setValue(param.getPasswordComplexity().getCode())); + } + //弱密码检查 + if (ObjectUtils.isNotEmpty(param.getWeakPasswordCheck()) && !StringUtils.equals( + PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_WEAK_PASSWORD_CHECK), + param.getWeakPasswordCheck().toString().toLowerCase())) { + list.add(new SettingEntity().setName(PASSWORD_POLICY_WEAK_PASSWORD_CHECK) + .setValue(String.valueOf(param.getWeakPasswordCheck()))); + } + //账户信息检查 + if (ObjectUtils.isNotEmpty(param.getIncludeAccountCheck()) && !StringUtils.equals( + PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_ACCOUNT_CHECK), + param.getIncludeAccountCheck().toString().toLowerCase())) { + list.add(new SettingEntity().setName(PASSWORD_POLICY_ACCOUNT_CHECK) + .setValue(String.valueOf(param.getIncludeAccountCheck()))); + } + //不能多少个以上相同字符 + if (ObjectUtils.isNotEmpty(param.getNotSameChars()) && !StringUtils.equals( + PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_NOT_SAME_CHARS), + param.getNotSameChars().toString())) { + list.add(new SettingEntity().setName(PASSWORD_POLICY_NOT_SAME_CHARS) + .setValue(String.valueOf(param.getNotSameChars()))); + } + //历史密码检查 + if (ObjectUtils.isNotEmpty(param.getHistoryPasswordCheck()) && !StringUtils.equals( + PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_HISTORY_PASSWORD_CHECK), + param.getHistoryPasswordCheck().toString())) { + list.add(new SettingEntity().setName(PASSWORD_POLICY_HISTORY_PASSWORD_CHECK) + .setValue(String.valueOf(param.getHistoryPasswordCheck()))); + } + //密码检查次数 + if (ObjectUtils.isNotEmpty(param.getHistoryPasswordCheckCount()) && !StringUtils.equals( + PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_HISTORY_PASSWORD_CHECK_COUNT), + param.getHistoryPasswordCheckCount().toString())) { + list.add(new SettingEntity().setName(PASSWORD_POLICY_HISTORY_PASSWORD_CHECK_COUNT) + .setValue(String.valueOf(param.getHistoryPasswordCheckCount()))); + } + //非法序列检查 + if (ObjectUtils.isNotEmpty(param.getIllegalSequenceCheck()) && !StringUtils.equals( + PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_ILLEGAL_SEQUENCE_CHECK), + param.getIllegalSequenceCheck().toString())) { + list.add(new SettingEntity().setName(PASSWORD_POLICY_ILLEGAL_SEQUENCE_CHECK) + .setValue(String.valueOf(param.getIllegalSequenceCheck()))); + } + //密码过期天数 + if (ObjectUtils.isNotEmpty(param.getPasswordValidDays()) + && !StringUtils.equals(PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_VALID_DAYS), + param.getPasswordValidDays().toString())) { + list.add(new SettingEntity().setName(PASSWORD_POLICY_VALID_DAYS) + .setValue(String.valueOf(param.getPasswordValidDays()))); + } + //密码过期提醒时间 + if (ObjectUtils.isNotEmpty(param.getPasswordValidWarnBeforeDays()) && !StringUtils.equals( + PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_VALID_WARN_BEFORE_DAYS), + param.getPasswordValidWarnBeforeDays().toString())) { + list.add(new SettingEntity().setName(PASSWORD_POLICY_VALID_WARN_BEFORE_DAYS) + .setValue(String.valueOf(param.getPasswordValidWarnBeforeDays()))); + } + //弱密码库 + if (StringUtils.isNotBlank(param.getCustomWeakPassword())) { + Set customWeakPasswords = new HashSet<>( + Arrays.stream(param.getCustomWeakPassword().split("\n")).toList()); + list.add(new SettingEntity().setName(PASSWORD_POLICY_CUSTOM_WEAK_PASSWORD) + .setValue(StringUtils.join(customWeakPasswords.toArray(), ","))); + } + return list; + } + + /** + * 实体转换为密码策略配置结果 + * + * @param list {@link List} + * @return {@link PasswordPolicyConfigResult} + */ + default PasswordPolicyConfigResult entityConvertToPasswordPolicyConfigResult(List list) { + Map map = list.stream().collect(Collectors.toMap(SettingEntity::getName, + SettingEntity::getValue, (key1, key2) -> key2)); + PasswordPolicyConfigResult result = new PasswordPolicyConfigResult(); + //最大长度 + result.setPasswordBiggestLength(map.containsKey(PASSWORD_POLICY_BIGGEST_LENGTH) + ? Integer.valueOf(map.get(PASSWORD_POLICY_BIGGEST_LENGTH)) + : Integer + .valueOf(PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_BIGGEST_LENGTH))); + + //最小长度 + result.setPasswordLeastLength(map.containsKey(PASSWORD_POLICY_LEAST_LENGTH) + ? Integer.valueOf(map.get(PASSWORD_POLICY_LEAST_LENGTH)) + : Integer.valueOf(PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_LEAST_LENGTH))); + + //密码复杂度 + result.setPasswordComplexity(map.containsKey(PASSWORD_POLICY_COMPLEXITY) + ? PasswordComplexityRule.getType(map.get(PASSWORD_POLICY_COMPLEXITY)) + : PasswordComplexityRule + .getType(PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_COMPLEXITY))); + + //弱密检查 + result.setWeakPasswordCheck(map.containsKey(PASSWORD_POLICY_WEAK_PASSWORD_CHECK) + ? Boolean.valueOf(map.get(PASSWORD_POLICY_WEAK_PASSWORD_CHECK)) + : Boolean.valueOf( + PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_WEAK_PASSWORD_CHECK))); + + //弱密码库 + result.setCustomWeakPassword(map.containsKey(PASSWORD_POLICY_CUSTOM_WEAK_PASSWORD) + ? StringUtils.join(map.get(PASSWORD_POLICY_CUSTOM_WEAK_PASSWORD).split(","), "\n") + : String.valueOf( + PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_CUSTOM_WEAK_PASSWORD))); + + //账户信息检查 + result.setIncludeAccountCheck(map.containsKey(PASSWORD_POLICY_ACCOUNT_CHECK) + ? Boolean.valueOf(map.get(PASSWORD_POLICY_ACCOUNT_CHECK)) + : Boolean.valueOf(PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_ACCOUNT_CHECK))); + + //密码不能多少个以上相同字符 + result.setNotSameChars(map.containsKey(PASSWORD_POLICY_NOT_SAME_CHARS) + ? Integer.valueOf(map.get(PASSWORD_POLICY_NOT_SAME_CHARS)) + : Integer + .valueOf(PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_NOT_SAME_CHARS))); + + //历史密码检查 + result.setHistoryPasswordCheck(map.containsKey(PASSWORD_POLICY_HISTORY_PASSWORD_CHECK) + ? Boolean.valueOf(map.get(PASSWORD_POLICY_HISTORY_PASSWORD_CHECK)) + : Boolean.valueOf( + PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_HISTORY_PASSWORD_CHECK))); + + //历史密码检查次数 + result.setHistoryPasswordCheckCount( + map.containsKey(PASSWORD_POLICY_HISTORY_PASSWORD_CHECK_COUNT) + ? Integer.valueOf(map.get(PASSWORD_POLICY_HISTORY_PASSWORD_CHECK_COUNT)) + : Integer.valueOf(PASSWORD_POLICY_DEFAULT_SETTINGS + .get(PASSWORD_POLICY_HISTORY_PASSWORD_CHECK_COUNT))); + + //非法序列检查 + result.setIllegalSequenceCheck(map.containsKey(PASSWORD_POLICY_ILLEGAL_SEQUENCE_CHECK) + ? Boolean.valueOf(map.get(PASSWORD_POLICY_ILLEGAL_SEQUENCE_CHECK)) + : Boolean.valueOf( + PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_ILLEGAL_SEQUENCE_CHECK))); + + //密码过期天数 + result.setPasswordValidDays(map.containsKey(PASSWORD_POLICY_VALID_DAYS) + ? Integer.valueOf(map.get(PASSWORD_POLICY_VALID_DAYS)) + : Integer.valueOf(PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_VALID_DAYS))); + + //密码过期提醒时间 + result + .setPasswordValidWarnBeforeDays(map.containsKey(PASSWORD_POLICY_VALID_WARN_BEFORE_DAYS) + ? Integer.valueOf(map.get(PASSWORD_POLICY_VALID_WARN_BEFORE_DAYS)) + : Integer.valueOf( + PASSWORD_POLICY_DEFAULT_SETTINGS.get(PASSWORD_POLICY_VALID_WARN_BEFORE_DAYS))); + return result; + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/SecuritySettingConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/SecuritySettingConverter.java new file mode 100644 index 00000000..34ce07a9 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/SecuritySettingConverter.java @@ -0,0 +1,238 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.setting; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import javax.validation.ValidationException; + +import org.apache.commons.lang3.ObjectUtils; +import org.mapstruct.Mapper; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import cn.topiam.employee.common.entity.setting.SettingEntity; +import cn.topiam.employee.common.enums.CaptchaProviderType; +import cn.topiam.employee.common.enums.MfaFactor; +import cn.topiam.employee.common.enums.MfaMode; +import cn.topiam.employee.console.pojo.result.setting.SecurityBasicConfigResult; +import cn.topiam.employee.console.pojo.result.setting.SecurityCaptchaConfigResult; +import cn.topiam.employee.console.pojo.result.setting.SecurityMfaConfigResult; +import cn.topiam.employee.console.pojo.save.setting.SecurityBasicSaveParam; +import cn.topiam.employee.console.pojo.save.setting.SecurityCaptchaSaveParam; +import cn.topiam.employee.console.pojo.save.setting.SecurityMfaSaveParam; +import cn.topiam.employee.core.security.captcha.CaptchaProviderConfig; +import cn.topiam.employee.core.security.captcha.geetest.GeeTestCaptchaProviderConfig; +import cn.topiam.employee.support.validation.ValidationHelp; +import static cn.topiam.employee.core.setting.constant.MfaSettingConstants.*; +import static cn.topiam.employee.core.setting.constant.SecuritySettingConstants.*; + +import static liquibase.sqlgenerator.core.MarkChangeSetRanGenerator.COMMA; + +/** + * 安全设置数据转换器 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/10/4 19:00 + */ +@Mapper(componentModel = "spring") +public interface SecuritySettingConverter { + + /** + * 实体转换为安全性高级配置结果 + * + * @param list {@link List} + * @return {@link SecurityBasicConfigResult} + */ + default SecurityBasicConfigResult entityConvertToSecurityBasicConfigResult(List list) { + //@formatter:off + SecurityBasicConfigResult result = new SecurityBasicConfigResult(); + //转MAP + Map map = list.stream().collect(Collectors.toMap(SettingEntity::getName, SettingEntity::getValue, (key1, key2) -> key2)); + //自动解锁时间 + result.setAutoUnlockTime(Integer.valueOf(map.containsKey(SECURITY_BASIC_AUTO_UNLOCK_TIME) ? map.get(SECURITY_BASIC_AUTO_UNLOCK_TIME) : SECURITY_BASIC_DEFAULT_SETTINGS.get(SECURITY_BASIC_AUTO_UNLOCK_TIME))); + //连续登录失败持续时间 + result.setLoginFailureDuration(Integer.valueOf(map.containsKey(SECURITY_BASIC_LOGIN_FAILURE_DURATION) ? map.get(SECURITY_BASIC_LOGIN_FAILURE_DURATION) : SECURITY_BASIC_DEFAULT_SETTINGS.get(SECURITY_BASIC_LOGIN_FAILURE_DURATION))); + //连续登录失败次数 + result.setLoginFailureCount(Integer.valueOf(map.containsKey(SECURITY_BASIC_LOGIN_FAILURE_COUNT) ? map.get(SECURITY_BASIC_LOGIN_FAILURE_COUNT) : SECURITY_BASIC_DEFAULT_SETTINGS.get(SECURITY_BASIC_LOGIN_FAILURE_COUNT))); + //会话有效时间 + result.setSessionValidTime(Integer.valueOf(map.containsKey(SECURITY_BASIC_SESSION_VALID_TIME) ? map.get(SECURITY_BASIC_SESSION_VALID_TIME) : SECURITY_BASIC_DEFAULT_SETTINGS.get(SECURITY_BASIC_SESSION_VALID_TIME))); + //短信验证码有效时间 + result.setVerifyCodeValidTime(Integer.valueOf(map.containsKey(VERIFY_CODE_VALID_TIME) ? map.get(VERIFY_CODE_VALID_TIME) : SECURITY_BASIC_DEFAULT_SETTINGS.get(VERIFY_CODE_VALID_TIME))); + //记住我有效时间(秒) + result.setRememberMeValidTime(Integer.valueOf(map.containsKey(SECURITY_BASIC_REMEMBER_ME_VALID_TIME) ? map.get(SECURITY_BASIC_REMEMBER_ME_VALID_TIME) : SECURITY_BASIC_DEFAULT_SETTINGS.get(SECURITY_BASIC_REMEMBER_ME_VALID_TIME))); + //用户并发数 + result.setSessionMaximum(Integer.valueOf(map.containsKey(SECURITY_SESSION_MAXIMUM) ? map.get(SECURITY_SESSION_MAXIMUM) : SECURITY_BASIC_DEFAULT_SETTINGS.get(SECURITY_SESSION_MAXIMUM))); + //@formatter:on + return result; + } + + /** + * 安全高级保存参数转换为实体 + * + * @param param {@link SecurityBasicSaveParam} + * @return {@link List} + */ + default List securityBasicSaveParamConvertToEntity(SecurityBasicSaveParam param) { + //@formatter:off + List list = new ArrayList<>(); + //会话有效时间 + if (ObjectUtils.isNotEmpty(param.getSessionValidTime())) { + list.add(new SettingEntity().setName(SECURITY_BASIC_SESSION_VALID_TIME).setValue(String.valueOf(param.getSessionValidTime()))); + } + //记住我有效时间(秒) + if (ObjectUtils.isNotEmpty(param.getRememberMeValidTime())) { + list.add(new SettingEntity().setName(SECURITY_BASIC_REMEMBER_ME_VALID_TIME).setValue(String.valueOf(param.getRememberMeValidTime()))); + } + //连续登录失败持续时间 + if (ObjectUtils.isNotEmpty(param.getLoginFailureDuration())) { + list.add(new SettingEntity().setName(SECURITY_BASIC_LOGIN_FAILURE_DURATION).setValue(String.valueOf(param.getLoginFailureDuration()))); + } + //连续登录失败次数 + if (ObjectUtils.isNotEmpty(param.getLoginFailureCount())) { + list.add(new SettingEntity().setName(SECURITY_BASIC_LOGIN_FAILURE_COUNT).setValue(String.valueOf(param.getLoginFailureCount()))); + } + //自动解锁时间(分) + if (ObjectUtils.isNotEmpty(param.getAutoUnlockTime())) { + list.add(new SettingEntity().setName(SECURITY_BASIC_AUTO_UNLOCK_TIME).setValue(String.valueOf(param.getAutoUnlockTime()))); + } + //用户并发数 + if (ObjectUtils.isNotEmpty(param.getSessionMaximum())) { + list.add(new SettingEntity().setName(SECURITY_SESSION_MAXIMUM).setValue(String.valueOf(param.getSessionMaximum()))); + } + //短信验证码有效时间(秒) + if (ObjectUtils.isNotEmpty(param.getSmsCodeValidTime())) { + list.add(new SettingEntity().setName(VERIFY_CODE_VALID_TIME).setValue(String.valueOf(param.getSmsCodeValidTime()))); + } + //@formatter:on + return list; + } + + /** + * Security Mfa Save Param 转换为实体 + * + * @param param {@link SecurityMfaSaveParam} + * @return {@link List} + */ + default List securityMfaSaveParamConvertToEntity(SecurityMfaSaveParam param) { + List list = new ArrayList<>(); + list.add(new SettingEntity().setName(MFA_MODE).setValue(param.getMode().getCode())); + if (!param.getFactors().isEmpty()) { + list.add(new SettingEntity().setName(MFA_FACTOR).setValue(param.getFactors().stream() + .map(MfaFactor::getCode).collect(Collectors.joining(",")))); + } + return list; + } + + /** + * 实体转换为安全 Mfa 配置结果 + * + * @param list {@link List} + * @return {@link SecurityMfaConfigResult} + */ + default SecurityMfaConfigResult entityConvertToSecurityMfaConfigResult(List list) { + SecurityMfaConfigResult result = new SecurityMfaConfigResult(); + //转MAP + Map map = list.stream().collect(Collectors.toMap(SettingEntity::getName, + SettingEntity::getValue, (key1, key2) -> key2)); + String manner = map.containsKey(MFA_FACTOR) ? map.get(MFA_FACTOR) + : MFA_SETTING_DEFAULT_SETTINGS.get(MFA_FACTOR); + List manners = new ArrayList<>(); + for (String s : manner.split(COMMA)) { + MfaFactor type = MfaFactor.getType(s); + if (!Objects.isNull(type)) { + manners.add(type); + } + } + if (!manners.isEmpty()) { + result.setFactors(manners); + } + result.setMode(MfaMode.getType(map.containsKey(MFA_MODE) ? map.get(MFA_MODE) + : MFA_SETTING_DEFAULT_SETTINGS.get(MFA_MODE))); + return result; + } + + /** + * 安全验证码保存参数转换为实体 + * + * @param param {@link SecurityCaptchaSaveParam} + * @return {@link List} + */ + default List securityCaptchaSaveParamConvertToEntity(SecurityCaptchaSaveParam param) { + List list = new ArrayList<>(); + try { + //极速验证 + if (CaptchaProviderType.GEE_TEST.equals(param.getProvider())) { + ObjectMapper objectMapper = new ObjectMapper(); + GeeTestCaptchaProviderConfig config = objectMapper.readValue( + param.getConfig().toJSONString(), GeeTestCaptchaProviderConfig.class); + ValidationHelp.ValidationResult validationResult = ValidationHelp + .validateEntity(config); + if (validationResult.isHasErrors()) { + throw new ValidationException(validationResult.getMessage()); + } + SettingEntity entity = new SettingEntity(); + entity.setName(CAPTCHA_SETTING_NAME); + // 指定序列化输入的类型 + objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), + ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); + entity.setValue(objectMapper.writeValueAsString(config)); + entity.setDesc("极速验证码配置"); + list.add(entity); + } + } + //JSON异常 + catch (JsonProcessingException e) { + throw new ValidationException(e); + } + return list; + } + + /** + * 实体转换为安全验证码配置结果 + * + * @param entity {@link SettingEntity} + * @return {@link SecurityCaptchaConfigResult} + */ + default SecurityCaptchaConfigResult entityConvertToSecurityCaptchaConfigResult(SettingEntity entity) { + if (Objects.isNull(entity)) { + return null; + } + SecurityCaptchaConfigResult result = new SecurityCaptchaConfigResult(); + ObjectMapper objectMapper = new ObjectMapper(); + // 指定序列化输入的类型 + objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), + ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); + try { + CaptchaProviderConfig config = objectMapper.readValue(entity.getValue(), + CaptchaProviderConfig.class); + result.setConfig(config); + result.setEnabled(true); + result.setProvider(config.getProvider()); + return result; + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/StorageSettingConverter.java b/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/StorageSettingConverter.java new file mode 100644 index 00000000..fbbae1e8 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/converter/setting/StorageSettingConverter.java @@ -0,0 +1,142 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.converter.setting; + +import java.util.Objects; + +import javax.validation.ValidationException; + +import org.mapstruct.Mapper; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import cn.topiam.employee.common.entity.setting.SettingEntity; +import cn.topiam.employee.common.storage.StorageConfig; +import cn.topiam.employee.common.storage.enums.StorageProvider; +import cn.topiam.employee.common.storage.impl.AliYunOssStorage; +import cn.topiam.employee.common.storage.impl.LocalStorage; +import cn.topiam.employee.common.storage.impl.MinIoStorage; +import cn.topiam.employee.common.storage.impl.QiNiuKodoStorage; +import cn.topiam.employee.console.pojo.result.setting.StorageProviderConfigResult; +import cn.topiam.employee.console.pojo.save.setting.StorageConfigSaveParam; +import cn.topiam.employee.support.validation.ValidationHelp; +import static cn.topiam.employee.core.setting.constant.StorageProviderSettingConstants.STORAGE_PROVIDER_KEY; + +/** + * 消息设置转换器 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/10/1 23:18 + */ +@Mapper(componentModel = "spring") +public interface StorageSettingConverter { + /** + * 存储提供商配置转实体类 + * + * @param param {@link StorageConfigSaveParam} + * @return {@link SettingEntity} + */ + default SettingEntity storageConfigSaveParamToEntity(StorageConfigSaveParam param) { + SettingEntity entity = new SettingEntity(); + StorageProvider provider = param.getProvider(); + ValidationHelp.ValidationResult validationResult = null; + StorageConfig.StorageConfigBuilder builder = StorageConfig.builder(); + builder.provider(provider); + ObjectMapper objectMapper = new ObjectMapper(); + try { + //阿里云 + if (provider.equals(StorageProvider.ALIYUN_OSS)) { + AliYunOssStorage.Config config = objectMapper + .readValue(param.getConfig().toJSONString(), AliYunOssStorage.Config.class); + builder.config(config); + validationResult = ValidationHelp.validateEntity(config); + } + //腾讯 + else if (provider.equals(StorageProvider.TENCENT_COS)) { + AliYunOssStorage.Config config = objectMapper + .readValue(param.getConfig().toJSONString(), AliYunOssStorage.Config.class); + builder.config(config); + validationResult = ValidationHelp.validateEntity(config); + } + //七牛 + else if (provider.equals(StorageProvider.QINIU_KODO)) { + QiNiuKodoStorage.Config config = objectMapper + .readValue(param.getConfig().toJSONString(), QiNiuKodoStorage.Config.class); + builder.config(config); + validationResult = ValidationHelp.validateEntity(config); + } + //MiNio + else if (provider.equals(StorageProvider.MINIO)) { + MinIoStorage.Config config = objectMapper + .readValue(param.getConfig().toJSONString(), MinIoStorage.Config.class); + builder.config(config); + validationResult = ValidationHelp.validateEntity(config); + } + //本机 + else if (provider.equals(StorageProvider.LOCAL)) { + LocalStorage.Config config = objectMapper + .readValue(param.getConfig().toJSONString(), LocalStorage.Config.class); + builder.config(config); + validationResult = ValidationHelp.validateEntity(config); + } + if (Objects.requireNonNull(validationResult).isHasErrors()) { + throw new ValidationException(validationResult.getMessage()); + } + entity.setName(STORAGE_PROVIDER_KEY); + // 指定序列化输入的类型 + objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), + ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); + entity.setValue(objectMapper.writeValueAsString(builder.build())); + entity.setDesc(provider.getDesc()); + return entity; + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + /** + * 实体转存储提供商配置 + * + * @param entity {@link SettingEntity} + * @return {@link StorageProviderConfigResult} + */ + default StorageProviderConfigResult entityToStorageProviderConfig(SettingEntity entity) { + if (Objects.isNull(entity)) { + return StorageProviderConfigResult.builder().enabled(false).build(); + } + ObjectMapper objectMapper = new ObjectMapper(); + // 指定序列化输入的类型 + objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), + ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); + try { + String value = entity.getValue(); + StorageConfig storageConfig = objectMapper.readValue(value, StorageConfig.class); + // 开启配置、并没有配置 + //@formatter:off + return StorageProviderConfigResult.builder() + .provider(storageConfig.getProvider()) + .enabled(true) + .config(storageConfig).build(); + //@formatter:on + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/listener/ConsoleAdminPasswordInitializeListener.java b/eiam-console/src/main/java/cn/topiam/employee/console/listener/ConsoleAdminPasswordInitializeListener.java new file mode 100644 index 00000000..4024e336 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/listener/ConsoleAdminPasswordInitializeListener.java @@ -0,0 +1,167 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.listener; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.util.Locale; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.lang.NonNull; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.JdkIdGenerator; + +import cn.topiam.employee.common.entity.setting.AdministratorEntity; +import cn.topiam.employee.common.enums.UserStatus; +import cn.topiam.employee.common.repository.setting.AdministratorRepository; +import cn.topiam.employee.support.trace.TraceUtils; +import static cn.topiam.employee.support.constant.EiamConstants.DEFAULT_ADMIN_USERNAME; +import static cn.topiam.employee.support.lock.LockAspect.getTopiamLockKeyPrefix; +import static cn.topiam.employee.support.util.CreateFileUtil.createFile; + +/** + * ConsoleAdminPasswordInitializeListener + * + * @author TopIAM + * Created by support@topiam.cn on 2022/11/26 21:44 + */ +@Component +public class ConsoleAdminPasswordInitializeListener implements + ApplicationListener { + + private final Logger logger = LoggerFactory + .getLogger(ConsoleAdminPasswordInitializeListener.class); + private static final String DIR_NAME = ".topiam"; + private static final String USER_HOME = "user.home"; + + @Override + @Transactional(rollbackFor = Exception.class) + public void onApplicationEvent(@NonNull ContextRefreshedEvent contextRefreshedEvent) { + //@formatter:off + String traceId = jdkIdGenerator.generateId().toString(); + TraceUtils.put(traceId); + RLock lock = redissonClient.getLock(getTopiamLockKeyPrefix()); + boolean tryLock = false; + try { + tryLock = lock.tryLock(1, TimeUnit.SECONDS); + if (tryLock){ + Optional optional = administratorRepository.findByUsername(DEFAULT_ADMIN_USERNAME); + if (optional.isEmpty()) { + String initPassword = jdkIdGenerator.generateId().toString().replace("-", "").toLowerCase(Locale.ENGLISH); + createFile(getInitialAdminPasswordFilePath()); + BufferedWriter stream = new BufferedWriter(new FileWriter(getInitialAdminPasswordFilePath())); + //清空 + stream.write(initPassword); + stream.flush(); + stream.close(); + String ls = System.lineSeparator(); + logger.info(ls + ls + "*************************************************************" + ls + + "*************************************************************" + ls + + "*************************************************************" + ls + + ls + + "TopIAM console initial setup is required. An admin user has been created and " + + "a password generated." + ls + + "Please use the following password to proceed to installation:" + ls + + ls + + initPassword + ls + + ls + + "This may also be found at: " + getInitialAdminPasswordFilePath() + ls + + ls + + "*************************************************************" + ls + + "*************************************************************" + ls + + "*************************************************************" + ls); + //保存管理员 + saveInitAdministrator(DEFAULT_ADMIN_USERNAME, initPassword); + } + } + + } catch (Exception exception) { + int exitCode = SpringApplication.exit(contextRefreshedEvent.getApplicationContext(), + () -> 0); + System.exit(exitCode); + } finally { + if (tryLock && lock.isLocked()) { + lock.unlock(); + } + TraceUtils.remove(); + } + //@formatter:on + } + + /** + * 保存管理员 + * + * @param username {@link String} + * @param password {@link String} + */ + private void saveInitAdministrator(String username, String password) { + AdministratorEntity administrator = new AdministratorEntity(); + administrator.setUsername(username); + administrator.setPassword(passwordEncoder.encode(password)); + administrator.setStatus(UserStatus.ENABLE); + administrator.setRemark( + "This administrator user is automatically created during system initialization."); + administratorRepository.save(administrator); + } + + public static String addSeparator(String dir) { + if (!dir.endsWith(File.separator)) { + dir += File.separator; + } + return dir; + } + + /** + * 获取初始化管理员密码文件路径 + * + * @return {@link String} + */ + public static String getInitialAdminPasswordFilePath() { + String path = addSeparator(System.getProperty(USER_HOME)) + DIR_NAME + File.separator + + "secrets" + File.separator; + return path + "initialAdminPassword"; + } + + private final JdkIdGenerator jdkIdGenerator = new JdkIdGenerator(); + + private final AdministratorRepository administratorRepository; + + private final PasswordEncoder passwordEncoder; + + private final RedissonClient redissonClient; + + public ConsoleAdminPasswordInitializeListener(AdministratorRepository administratorRepository, + PasswordEncoder passwordEncoder, + RedissonClient redissonClient) { + this.administratorRepository = administratorRepository; + this.passwordEncoder = passwordEncoder; + this.redissonClient = redissonClient; + } + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/listener/package-info.java b/eiam-console/src/main/java/cn/topiam/employee/console/listener/package-info.java new file mode 100644 index 00000000..8d8f04ee --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/listener/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.listener; \ No newline at end of file diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/package-info.java b/eiam-console/src/main/java/cn/topiam/employee/console/package-info.java new file mode 100644 index 00000000..e93c9eac --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console; \ No newline at end of file diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/other/IdentitySourceConfigValidatorParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/other/IdentitySourceConfigValidatorParam.java new file mode 100644 index 00000000..836d5fbc --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/other/IdentitySourceConfigValidatorParam.java @@ -0,0 +1,64 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.other; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotNull; + +import org.springdoc.api.annotations.ParameterObject; + +import com.alibaba.fastjson2.JSONObject; + +import cn.topiam.employee.common.enums.identityprovider.IdentitySourceProvider; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 身份源配置验证器入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/4/13 23:01 + */ +@Data +@Schema(description = "身份源配置验证器入参") +@ParameterObject +public class IdentitySourceConfigValidatorParam implements Serializable { + + @Serial + private static final long serialVersionUID = -360733000329499789L; + + /** + * 身份源提供商 + */ + @NotNull(message = "身份源提供商不能为空") + @Parameter(description = "身份源提供商") + private IdentitySourceProvider provider; + + /** + * 配置 + */ + @NotNull(message = "验证配置不能为空") + @Parameter(description = "配置") + private JSONObject config; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/other/OrganizationExcelData.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/other/OrganizationExcelData.java new file mode 100644 index 00000000..6e67ffa8 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/other/OrganizationExcelData.java @@ -0,0 +1,79 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.other; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotNull; + +import org.hibernate.validator.constraints.Length; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; + +import cn.topiam.employee.common.enums.OrganizationType; + +import lombok.Data; + +/** + * 组织架构Excel + * + * @author TopIAM + */ +@Data +public class OrganizationExcelData implements Serializable { + + @Serial + private static final long serialVersionUID = -1636371891351203058L; + private static final String PROMPT = + // @formatter:off + "填写须知:\n" + + " <1>不能在本excel表中对部门信息类别进行增加、删除、修改;\n" + + " <2>红色字段为必填字段,黑色字段为选填字段;\n" + + " <3>部门:上下级部门间用\"-\"隔开,且从最上级部门开始,例如\"研发部-济南分部\";"; + // @formatter:no + + @ExcelProperty(value = {"上级架构"}, index = 0) + @ColumnWidth(value = 30) + @Length(max = 100) + private String parentId; + + /** + * 名称 + */ + @ExcelProperty(value = {"架构名称"}, index = 1) + @ColumnWidth(value = 15) + @Length(max = 40) + private String name; + + /** + * 类型 + */ + @ColumnWidth(value = 15) + @NotNull + private OrganizationType type; + + /** + * 外部ID + */ + @ExcelProperty(value = {"外部ID"}, index = 3) + @ColumnWidth(value = 15) + @Length(max = 128) + private String externalId; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/other/package-info.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/other/package-info.java new file mode 100644 index 00000000..dc8e885c --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/other/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.other; \ No newline at end of file diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/package-info.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/package-info.java new file mode 100644 index 00000000..0d959228 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/package-info.java @@ -0,0 +1,24 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +/** + * pojo + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/12 22:33 + */ +package cn.topiam.employee.console.pojo; \ No newline at end of file diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/account/UserGroupListQuery.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/account/UserGroupListQuery.java new file mode 100644 index 00000000..d47422df --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/account/UserGroupListQuery.java @@ -0,0 +1,54 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.query.account; + +import java.io.Serial; +import java.io.Serializable; + +import org.springdoc.api.annotations.ParameterObject; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 查询用户列表入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:08 + */ +@Data +@Schema(description = "查询用户组列表入参") +@ParameterObject +public class UserGroupListQuery implements Serializable { + @Serial + private static final long serialVersionUID = -7110595216804896858L; + + /** + * 用户组名称 + */ + @Parameter(description = "用户组名称") + private String name; + + /** + * 用户组编码 + */ + @Parameter(description = "用户组编码") + private String code; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/analysis/AnalysisQuery.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/analysis/AnalysisQuery.java new file mode 100644 index 00000000..7181071b --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/analysis/AnalysisQuery.java @@ -0,0 +1,85 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.query.analysis; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.time.Period; + +import javax.validation.constraints.NotNull; + +import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval; +import org.springframework.format.annotation.DateTimeFormat; + +import lombok.Data; +import lombok.Getter; + +import io.swagger.v3.oas.annotations.media.Schema; +import static cn.topiam.employee.support.constant.EiamConstants.DEFAULT_DATE_TIME_FORMATTER_PATTERN; + +/** + * 统计查询入惨 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/11/22 23:16 + */ +@Data +@Schema(description = "统计查询入惨") +public class AnalysisQuery implements Serializable { + + /** + * 开始日期 + */ + @NotNull(message = "开始日期不能为空") + @Schema(description = "开始日期") + @DateTimeFormat(pattern = DEFAULT_DATE_TIME_FORMATTER_PATTERN) + private LocalDateTime startTime; + + /** + * 截止日期 + */ + @NotNull(message = "截止日期不能为空") + @Schema(description = "截止日期") + @DateTimeFormat(pattern = DEFAULT_DATE_TIME_FORMATTER_PATTERN) + private LocalDateTime endTime; + + public Interval getTimeInterval() { + Period interval = Period.between(startTime.toLocalDate(), endTime.toLocalDate()); + boolean today = interval.getDays() < 1; + boolean isDay = interval.getMonths() < 1; + return today ? Interval.HOUR : isDay ? Interval.DAY : Interval.MONTH; + } + + @Getter + public enum Interval { + + HOUR(DateHistogramInterval.HOUR, "HH时"), + + DAY(DateHistogramInterval.DAY, "dd日"), + + MONTH(DateHistogramInterval.MONTH, "MM月"); + + private final DateHistogramInterval type; + private final String format; + + Interval(DateHistogramInterval type, String format) { + this.type = type; + this.format = format; + } + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppCertQuery.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppCertQuery.java new file mode 100644 index 00000000..828777cb --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppCertQuery.java @@ -0,0 +1,56 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.query.app; + +import java.io.Serializable; + +import javax.validation.constraints.NotBlank; + +import org.springdoc.api.annotations.ParameterObject; + +import cn.topiam.employee.common.enums.app.AppCertUsingType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 查询应用证书列表入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:08 + */ +@Data +@Schema(description = "查询应用证书列表入参") +@ParameterObject +public class AppCertQuery implements Serializable { + + /** + * 应用ID + */ + @NotBlank(message = "应用ID不能为空") + @Parameter(description = "应用ID") + private String appId; + + /** + * 使用类型 + */ + @Parameter(description = "使用类型") + private AppCertUsingType usingType; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppPermissionActionListQuery.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppPermissionActionListQuery.java new file mode 100644 index 00000000..4be2cc27 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppPermissionActionListQuery.java @@ -0,0 +1,65 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.query.app; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotEmpty; + +import org.springdoc.api.annotations.ParameterObject; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 查询权限列表入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:08 + */ +@Data +@Schema(description = "查询权限列表入参") +@ParameterObject +public class AppPermissionActionListQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 4307939244290315206L; + + /** + * 资源ID + */ + @Parameter(description = "资源ID") + private String id; + + /** + * 资源名称 + */ + @Parameter(description = "资源名称") + private String name; + + /** + * 应用ID + */ + @Parameter(description = "应用ID") + @NotEmpty(message = "应用ID不能为空") + private String appId; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppPermissionRoleListQuery.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppPermissionRoleListQuery.java new file mode 100644 index 00000000..1b52edb7 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppPermissionRoleListQuery.java @@ -0,0 +1,67 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.query.app; + +import java.io.Serializable; + +import javax.validation.constraints.NotNull; + +import org.springdoc.api.annotations.ParameterObject; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 分页查询角色入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:08 + */ +@Data +@Schema(description = "查询角色列表入参") +@ParameterObject +public class AppPermissionRoleListQuery implements Serializable { + + /** + * 角色名称 + */ + @Parameter(description = "角色名称") + private String name; + + /** + * 角色编码 + */ + @Parameter(description = "角色编码") + private String code; + + /** + * 所属应用 + */ + @NotNull(message = "请选择角色所属应用") + @Parameter(description = "所属应用") + private Long appId; + + /** + * 是否启用 + */ + @Parameter(description = "是否启用") + private Boolean enabled; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppQuery.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppQuery.java new file mode 100644 index 00000000..3d5eefa4 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppQuery.java @@ -0,0 +1,53 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.query.app; + +import java.io.Serializable; + +import org.springdoc.api.annotations.ParameterObject; + +import cn.topiam.employee.common.enums.app.AppProtocol; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 查询应用列表入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:08 + */ +@Data +@Schema(description = "查询应用列表入参") +@ParameterObject +public class AppQuery implements Serializable { + + /** + * 应用名称 + */ + @Parameter(description = "应用名称") + private String name; + + /** + * 协议类型 + */ + @Parameter(description = "协议类型") + private AppProtocol protocol; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppResourceListQuery.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppResourceListQuery.java new file mode 100644 index 00000000..7a099ff7 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/app/AppResourceListQuery.java @@ -0,0 +1,61 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.query.app; + +import java.io.Serializable; + +import javax.validation.constraints.NotNull; + +import org.springdoc.api.annotations.ParameterObject; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 分页查询资源入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:08 + */ +@Data +@Schema(description = "查询权限资源列表入参") +@ParameterObject +public class AppResourceListQuery implements Serializable { + + /** + * 资源名称 + */ + @Parameter(description = "资源名称") + private String name; + + /** + * 所属应用 + */ + @NotNull(message = "请选择资源所属应用") + @Parameter(description = "所属应用") + private Long appId; + + /** + * 是否启用 + */ + @Parameter(description = "是否启用") + private Boolean enabled; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/authentication/IdentityProviderListQuery.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/authentication/IdentityProviderListQuery.java new file mode 100644 index 00000000..53c84514 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/authentication/IdentityProviderListQuery.java @@ -0,0 +1,59 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.query.authentication; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotNull; + +import org.springdoc.api.annotations.ParameterObject; + +import cn.topiam.employee.common.enums.IdentityProviderCategory; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/3/21 20:52 + */ +@Data +@Schema(description = "查询认证源列表入参") +@ParameterObject +public class IdentityProviderListQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1191998425971892318L; + + /** + * 认证源ID + */ + @Parameter(description = "认证源名称") + private String name; + + /** + * 认证源类型 + */ + @Parameter(description = "认证源分类") + @NotNull(message = "认证源分类不能为空") + private IdentityProviderCategory category; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/authentication/package-info.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/authentication/package-info.java new file mode 100644 index 00000000..e11eb2ec --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/authentication/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.query.authentication; \ No newline at end of file diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/identity/IdentitySourceEventRecordListQuery.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/identity/IdentitySourceEventRecordListQuery.java new file mode 100644 index 00000000..8c03b71c --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/identity/IdentitySourceEventRecordListQuery.java @@ -0,0 +1,70 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.query.identity; + +import java.io.Serial; +import java.io.Serializable; + +import org.springdoc.api.annotations.ParameterObject; + +import cn.topiam.employee.common.enums.SyncStatus; +import cn.topiam.employee.common.enums.identitysource.IdentitySourceActionType; +import cn.topiam.employee.common.enums.identitysource.IdentitySourceObjectType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 查询身份源事件记录列表入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/14 01:29 + */ +@Data +@Schema(description = "查询身份源事件记录列表入参") +@ParameterObject +public class IdentitySourceEventRecordListQuery implements Serializable { + @Serial + private static final long serialVersionUID = -7110595216804896858L; + + /** + * 身份源ID + */ + @Parameter(description = "身份源ID") + private String identitySourceId; + + /** + * 动作类型 + */ + @Parameter(description = "动作类型") + private IdentitySourceActionType actionType; + + /** + * 对象类型 + */ + @Parameter(description = "对象类型") + private IdentitySourceObjectType objectType; + + /** + * 事件状态 + */ + @Parameter(description = "事件状态") + private SyncStatus status; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/identity/IdentitySourceListQuery.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/identity/IdentitySourceListQuery.java new file mode 100644 index 00000000..10d58b30 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/identity/IdentitySourceListQuery.java @@ -0,0 +1,48 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.query.identity; + +import java.io.Serial; +import java.io.Serializable; + +import org.springdoc.api.annotations.ParameterObject; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 查询身份源列表入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/14 01:29 + */ +@Data +@Schema(description = "查询身份源列表入参") +@ParameterObject +public class IdentitySourceListQuery implements Serializable { + @Serial + private static final long serialVersionUID = -7110595216804896858L; + + /** + * 身份源名称 + */ + @Parameter(description = "身份源名称") + private String name; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/identity/IdentitySourceSyncHistoryListQuery.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/identity/IdentitySourceSyncHistoryListQuery.java new file mode 100644 index 00000000..2fa9e188 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/identity/IdentitySourceSyncHistoryListQuery.java @@ -0,0 +1,73 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.query.identity; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotBlank; + +import org.springdoc.api.annotations.ParameterObject; + +import cn.topiam.employee.common.enums.SyncStatus; +import cn.topiam.employee.common.enums.TriggerType; +import cn.topiam.employee.common.enums.identitysource.IdentitySourceObjectType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 查询身份源列表入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/14 01:29 + */ +@Data +@Schema(description = "查询身份源同步历史列表入参") +@ParameterObject +public class IdentitySourceSyncHistoryListQuery implements Serializable { + @Serial + private static final long serialVersionUID = -7110595216804896858L; + + /** + * 身份源ID + */ + @NotBlank(message = "身份源ID不能为空") + @Parameter(description = "身份源ID") + private String identitySourceId; + + /** + * 对象类型 + */ + @Parameter(description = "对象类型") + private IdentitySourceObjectType objectType; + + /** + * 触发类型 + */ + @Parameter(description = "触发类型") + private TriggerType triggerType; + + /** + * 状态 + */ + @Parameter(description = "触发类型") + private SyncStatus status; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/identity/IdentitySourceSyncRecordListQuery.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/identity/IdentitySourceSyncRecordListQuery.java new file mode 100644 index 00000000..e8d7a264 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/identity/IdentitySourceSyncRecordListQuery.java @@ -0,0 +1,74 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.query.identity; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotBlank; + +import org.springdoc.api.annotations.ParameterObject; + +import cn.topiam.employee.common.enums.SyncStatus; +import cn.topiam.employee.common.enums.identitysource.IdentitySourceActionType; +import cn.topiam.employee.common.enums.identitysource.IdentitySourceObjectType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 查询身份源同步详情入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/14 01:29 + */ +@Data +@Schema(description = "查询身份源同步详情入参") +@ParameterObject +public class IdentitySourceSyncRecordListQuery implements Serializable { + @Serial + private static final long serialVersionUID = -7110595216804896858L; + + /** + * 历史记录ID + */ + @NotBlank(message = "历史记录ID不能为空") + @Parameter(description = "历史记录ID") + private String syncHistoryId; + + /** + * 对象类型 + */ + @Parameter(description = "对象类型") + private IdentitySourceObjectType objectType; + + /** + * 操作类型 + */ + @Parameter(description = "操作类型") + private IdentitySourceActionType actionType; + + /** + * 触发类型 + */ + @Parameter(description = "状态") + private SyncStatus status; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/package-info.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/package-info.java new file mode 100644 index 00000000..173b615b --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.query; \ No newline at end of file diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/setting/AdministratorListQuery.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/setting/AdministratorListQuery.java new file mode 100644 index 00000000..74dcba16 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/query/setting/AdministratorListQuery.java @@ -0,0 +1,40 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.query.setting; + +import org.springdoc.api.annotations.ParameterObject; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2021/11/14 19:36 + */ +@Data +@Schema(description = "管理员列表查询") +@ParameterObject +public class AdministratorListQuery { + /** + * username + */ + @Parameter(description = "用户名") + private String username; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/OrganizationChildResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/OrganizationChildResult.java new file mode 100644 index 00000000..a45bcdee --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/OrganizationChildResult.java @@ -0,0 +1,106 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.account; + +import java.io.Serial; +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 获取子组织 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 21:27 + */ +@Data +@Schema(description = "获取子组织") +public class OrganizationChildResult implements Serializable { + @Serial + private static final long serialVersionUID = -150631305460653395L; + + /** + * 主键ID + */ + @Parameter(description = "ID") + private String id; + + /** + * 名称 + */ + @Parameter(description = "名称") + private String name; + + /** + * 父级 + */ + @Parameter(description = "父级") + private String parentId; + + /** + * 显示路径 + */ + @Parameter(description = "显示路径") + private String displayPath; + + /** + * 编码 + */ + @Parameter(description = "编码") + private String code; + + /** + * 外部ID + */ + @Parameter(description = "外部ID") + private String externalId; + /** + * 来源 + */ + @Parameter(description = "类型") + private String type; + /** + * 来源 + */ + @Parameter(description = "数据来源") + private String dataOrigin; + + /** + * 排序 + */ + @Parameter(description = "排序") + private Integer order; + + /** + * 是否启用 + */ + @Parameter(description = "是否启用") + private Boolean enabled; + /** + * 是否叶子节点 + */ + @JsonProperty(value = "isLeaf") + @Parameter(description = "是否叶子节点") + private Boolean leaf; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/OrganizationResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/OrganizationResult.java new file mode 100644 index 00000000..6dc0bb0a --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/OrganizationResult.java @@ -0,0 +1,81 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.account; + +import java.io.Serial; +import java.io.Serializable; + +import cn.topiam.employee.common.enums.OrganizationType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 获取组织 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 21:27 + */ +@Data +@Schema(description = "获取组织") +public class OrganizationResult implements Serializable { + @Serial + private static final long serialVersionUID = -150631305460653395L; + /** + * 主键ID + */ + @Parameter(description = "ID") + private String id; + /** + * key + */ + @Parameter(description = "名称") + private String name; + + /** + * 显示路径 + */ + @Parameter(description = "显示路径") + private String displayPath; + /** + * 编码 + */ + @Parameter(description = "编码") + private String code; + + /** + * 排序 + */ + @Parameter(description = "排序") + private String order; + + /** + * 组织机构类型 + */ + @Parameter(description = "机构类型") + private OrganizationType type; + + /** + * 备注 + */ + @Parameter(description = "备注") + private String remark; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/OrganizationRootResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/OrganizationRootResult.java new file mode 100644 index 00000000..4531005d --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/OrganizationRootResult.java @@ -0,0 +1,106 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.account; + +import java.io.Serial; +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 获取根组织 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 21:27 + */ +@Data +@Schema(description = "获取根组织") +public class OrganizationRootResult implements Serializable { + @Serial + private static final long serialVersionUID = -150631305460653395L; + + /** + * 主键ID + */ + @Parameter(description = "ID") + private String id; + + /** + * 名称 + */ + @Parameter(description = "名称") + private String name; + + /** + * 父级 + */ + @Parameter(description = "父级") + private String parentId; + + /** + * 显示路径 + */ + @Parameter(description = "显示路径") + private String displayPath; + + /** + * 编码 + */ + @Parameter(description = "编码") + private String code; + + /** + * 外部ID + */ + @Parameter(description = "外部ID") + private String externalId; + /** + * 来源 + */ + @Parameter(description = "类型") + private String type; + /** + * 来源 + */ + @Parameter(description = "数据来源") + private String dataOrigin; + + /** + * 排序 + */ + @Parameter(description = "排序") + private String order; + + /** + * 是否启用 + */ + @Parameter(description = "是否启用") + private Boolean enabled; + /** + * 是否叶子节点 + */ + @JsonProperty(value = "isLeaf") + @Parameter(description = "是否叶子节点") + private Boolean leaf; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/OrganizationTreeResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/OrganizationTreeResult.java new file mode 100644 index 00000000..f77306ed --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/OrganizationTreeResult.java @@ -0,0 +1,115 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.account; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 查询组织架构树结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 21:27 + */ +@Data +@Schema(description = "查询组织架构树结果") +public class OrganizationTreeResult implements Serializable { + + @Serial + private static final long serialVersionUID = 5599721546299678344L; + /** + * 主键ID + */ + @Parameter(description = "ID") + private String id; + + /** + * 名称 + */ + @Parameter(description = "名称") + private String name; + + /** + * 父级 + */ + @Parameter(description = "父级") + private String parentId; + + /** + * 显示路径 + */ + @Parameter(description = "显示路径") + private String displayPath; + + /** + * 编码 + */ + @Parameter(description = "编码") + private String code; + + /** + * 外部ID + */ + @Parameter(description = "外部ID") + private String externalId; + + /** + * 类型 + */ + @Parameter(description = "类型") + private String type; + + /** + * 来源 + */ + @Parameter(description = "数据来源") + private String dataOrigin; + + /** + * 排序 + */ + @Parameter(description = "排序") + private Integer order; + + /** + * 是否启用 + */ + @Parameter(description = "是否启用") + private Boolean enabled; + + /** + * 是否叶子节点 + */ + @JsonProperty(value = "isLeaf") + @Parameter(description = "是否叶子节点") + private Boolean leaf; + + /** + * 子级列表 + */ + @Parameter(description = "子级列表") + private List children; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserGroupListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserGroupListResult.java new file mode 100644 index 00000000..5045f979 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserGroupListResult.java @@ -0,0 +1,64 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.account; + +import java.io.Serial; +import java.io.Serializable; + +import lombok.Data; +import lombok.experimental.Accessors; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 用户分页查询结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:08 + */ +@Data +@Accessors(chain = true) +@Schema(description = "查询用户分组列表") +public class UserGroupListResult implements Serializable { + + @Serial + private static final long serialVersionUID = 3320953184046791392L; + /** + * 用户ID + */ + @Parameter(description = "用户组ID") + private String id; + /** + * 用户组名称 + */ + @Parameter(description = "用户组名称") + private String name; + + /** + * 用户组编码 + */ + @Parameter(description = "用户组编码") + private String code; + + /** + * 备注 + */ + @Parameter(description = "备注") + private String remark; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserGroupMemberListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserGroupMemberListResult.java new file mode 100644 index 00000000..3fa8a7e0 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserGroupMemberListResult.java @@ -0,0 +1,80 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.account; + +import java.io.Serial; +import java.io.Serializable; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 查询用户详情结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:22 + */ +@Data +@Schema(description = "查询用户详情结果") +public class UserGroupMemberListResult implements Serializable { + @Serial + private static final long serialVersionUID = -5144879825451360221L; + /** + * ID + */ + @Parameter(description = "ID") + private String id; + + /** + * 用户名 + */ + @Parameter(description = "用户名") + private String username; + + /** + * 姓名 + */ + @Parameter(description = "姓名") + private String fullName; + + /** + * 邮箱 + */ + @Parameter(description = "邮箱") + private String email; + + /** + * 手机号 + */ + @Parameter(description = "手机号") + private String phone; + + /** + * 头像URL + */ + @Parameter(description = "头像URL") + private String avatar; + + /** + * 组织显示目录 + */ + @Parameter(description = "组织显示目录") + private String orgDisplayPath; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserGroupResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserGroupResult.java new file mode 100644 index 00000000..9d266292 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserGroupResult.java @@ -0,0 +1,61 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.account; + +import java.io.Serial; +import java.io.Serializable; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 查询用户分组详情 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:22 + */ +@Data +@Schema(description = "根据ID查询用户分组结果") +public class UserGroupResult implements Serializable { + @Serial + private static final long serialVersionUID = -5144879825451360221L; + /** + * 用户ID + */ + @Parameter(description = "用户组ID") + private String id; + /** + * 用户组名称 + */ + @Parameter(description = "用户组名称") + private String name; + + /** + * 用户组编码 + */ + @Parameter(description = "用户组编码") + private String code; + + /** + * 备注 + */ + @Parameter(description = "备注") + private String remark; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserListResult.java new file mode 100644 index 00000000..57c9a17d --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserListResult.java @@ -0,0 +1,130 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.account; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import lombok.Data; +import lombok.experimental.Accessors; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import static cn.topiam.employee.support.constant.EiamConstants.DEFAULT_DATE_TIME_FORMATTER_PATTERN; + +/** + * 用户分页查询结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:08 + */ +@Data +@Accessors(chain = true) +@Schema(description = "分页查询用户结果") +public class UserListResult implements Serializable { + + @Serial + private static final long serialVersionUID = 3320953184046791392L; + /** + * 用户ID + */ + @Parameter(description = "用户ID") + private String id; + + /** + * 用户名 + */ + @Parameter(description = "用户名") + private String username; + + /** + * 姓名 + */ + @Parameter(description = "姓名") + private String fullName; + + /** + * 邮箱 + */ + @Parameter(description = "邮箱") + private String email; + + /** + * 手机号 + */ + @Parameter(description = "手机号") + private String phone; + + /** + * 头像URL + */ + @Parameter(description = "头像URL") + private String avatar; + + /** + * 状态 + */ + @Parameter(description = "状态") + private String status; + + /** + * 邮箱验证有效 + */ + @Parameter(description = "邮箱验证") + private Boolean emailVerified; + + /** + * 手机验证 + */ + @Parameter(description = "手机验证") + private Boolean phoneVerified; + + /** + * 认证次数 + */ + @Parameter(description = "认证次数") + private Long authTotal; + /** + * 数据来源 + */ + + @Parameter(description = "数据来源") + private String dataOrigin; + + /** + * 上次认证时间 + */ + @JsonFormat(pattern = DEFAULT_DATE_TIME_FORMATTER_PATTERN) + @Parameter(description = "上次认证时间") + private LocalDateTime lastAuthTime; + + /** + * 目录 + */ + @Parameter(description = "组织机构目录") + private String orgDisplayPath; + + /** + * 最后修改密码时间 + */ + @Parameter(description = "最后修改密码时间") + private LocalDateTime lastUpdatePasswordTime; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserLoginAuditListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserLoginAuditListResult.java new file mode 100644 index 00000000..45069ec0 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserLoginAuditListResult.java @@ -0,0 +1,74 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.account; + +import java.time.LocalDateTime; + +import cn.topiam.employee.audit.enums.EventStatus; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 用户登录日志返回结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2022年11月13日14:49:35 + */ +@Data +@Schema(description = "用户登录日志返回结果") +public class UserLoginAuditListResult { + + /** + * 应用名称 + */ + @Parameter(description = "应用名称") + private String appName; + + /** + * 客户端IP + */ + @Parameter(description = "客户端IP") + private String clientIp; + + /** + * 登录结果 + */ + @Parameter(description = "浏览器") + private String browser; + + /** + * 位置 + */ + @Parameter(description = "位置") + private String location; + + /** + * 事件状态 + */ + @Parameter(description = "事件状态") + private EventStatus eventStatus; + + /** + * 事件时间 + */ + @Parameter(description = "事件时间") + private LocalDateTime eventTime; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserResult.java new file mode 100644 index 00000000..78f63830 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/account/UserResult.java @@ -0,0 +1,170 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.account; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDate; +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import static cn.topiam.employee.support.constant.EiamConstants.DEFAULT_DATE_TIME_FORMATTER_PATTERN; + +/** + * 根据ID查询用户结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:22 + */ +@Data +@Schema(description = "根据ID查询用户结果") +public class UserResult implements Serializable { + @Serial + private static final long serialVersionUID = -5144879825451360221L; + /** + * ID + */ + @Parameter(description = "ID") + private String id; + /** + * 用户名 + */ + @Parameter(description = "用户名") + private String username; + + /** + * 邮箱 + */ + @Parameter(description = "邮箱") + private String email; + + /** + * 手机号 + */ + @Parameter(description = "手机号") + private String phone; + + /** + * 头像URL + */ + @Parameter(description = "头像URL") + private String avatar; + + /** + * 状态 + */ + @Parameter(description = "状态") + private String status; + + /** + * 姓名 + */ + @Parameter(description = "姓名") + private String fullName; + + /** + * 昵称 + */ + @Parameter(description = "昵称") + private String nickName; + + /** + * 身份证号 + */ + @Parameter(description = "身份证号") + private String idCard; + + /** + * 地址 + */ + @Parameter(description = "地址") + private String address; + + /** + * 认证次数 + */ + @Parameter(description = "认证次数") + private String authTotal; + + /** + * 最后登录 IP + */ + @Parameter(description = "最后登录 IP") + private String lastAuthIp; + + /** + * 上次认证时间 + */ + @Parameter(description = "上次认证时间") + @JsonFormat(pattern = DEFAULT_DATE_TIME_FORMATTER_PATTERN) + private LocalDateTime lastAuthTime; + + /** + * 数据来源 + */ + @Parameter(description = "数据来源") + private String dataOrigin; + + /** + * 手机有效 + */ + @Parameter(description = "手机验证有效") + private Boolean phoneVerified; + + /** + * 邮箱验证有效 + */ + @Parameter(description = "邮箱验证有效") + private Boolean emailVerified; + + /** + * 外部ID + */ + @Parameter(description = "外部ID") + private String externalId; + + /** + * 过期时间 + */ + @Parameter(description = "过期时间") + private LocalDate expireDate; + + /** + * 创建时间 + */ + @Parameter(description = "创建时间") + private LocalDateTime createTime; + + /** + * 最后修改时间 + */ + @Parameter(description = "修改时间") + private LocalDateTime updateTime; + + /** + * 备注 + */ + @Parameter(description = "备注") + private String remark; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/analysis/AppVisitRankResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/analysis/AppVisitRankResult.java new file mode 100644 index 00000000..bcb50233 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/analysis/AppVisitRankResult.java @@ -0,0 +1,49 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.analysis; + +import java.io.Serializable; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 应用热点访问结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/11/22 23:16 + */ +@Data +@AllArgsConstructor +@Schema(description = "应用访问频次") +public class AppVisitRankResult implements Serializable { + + /** + * 应用名称 + */ + @Schema(description = "应用名称") + private String name; + + /** + * 访问次数 + */ + @Schema(description = "访问次数") + private Long count; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/analysis/AuthnHotProviderResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/analysis/AuthnHotProviderResult.java new file mode 100644 index 00000000..5a8ac552 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/analysis/AuthnHotProviderResult.java @@ -0,0 +1,49 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.analysis; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 热点认证方式结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/11/22 23:16 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "热点认证方式结果") +public class AuthnHotProviderResult { + + /** + * 认证方式 + */ + @Schema(description = "认证方式") + private String name; + + /** + * 使用次数 + */ + @Schema(description = "使用次数") + private Long count; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/analysis/AuthnQuantityResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/analysis/AuthnQuantityResult.java new file mode 100644 index 00000000..0a464366 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/analysis/AuthnQuantityResult.java @@ -0,0 +1,55 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.analysis; + +import java.io.Serializable; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 认证量统计结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/11/22 23:16 + */ +@Data +@AllArgsConstructor +@Schema(description = "认证量统计结果") +public class AuthnQuantityResult implements Serializable { + + /** + * 名称 + */ + @Schema(description = "名称") + private String name; + + /** + * 数量 + */ + @Schema(description = "数量") + private Long count; + + /** + * 状态 + */ + @Schema(description = "状态") + private String status; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/analysis/OverviewResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/analysis/OverviewResult.java new file mode 100644 index 00000000..4b0cf223 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/analysis/OverviewResult.java @@ -0,0 +1,59 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.analysis; + +import java.io.Serializable; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 概述总计结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/11/22 23:16 + */ +@Data +@Schema(description = "概述总计结果") +public class OverviewResult implements Serializable { + + /** + * 今日认证量 + */ + @Schema(description = "今日认证量") + private Long todayAuthnCount; + + /** + * 认证源总数 + */ + @Schema(description = "认证源总数") + private Long idpCount; + + /** + * 用户数量 + */ + @Schema(description = "用户数量") + private Long userCount; + + /** + * 应用数量 + */ + @Schema(description = "应用数量") + private Long appCount; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppAccessPolicyResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppAccessPolicyResult.java new file mode 100644 index 00000000..03e1ce37 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppAccessPolicyResult.java @@ -0,0 +1,98 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.app; + +import java.time.LocalDateTime; + +import cn.topiam.employee.common.enums.PolicySubjectType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 应用访问授权策略结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/9/27 19:29 + */ +@Data +@Schema(description = "应用访问授权策略结果") +public class AppAccessPolicyResult { + + /** + * id + */ + @Schema(description = "id") + private String id; + + /** + * 应用ID + */ + @Schema(description = "应用ID") + private Long appId; + + /** + * 应用名称 + */ + @Schema(description = "应用名称") + private String appName; + + /** + * 模板 + */ + @Schema(description = "应用模版") + private String appTemplate; + + /** + * 协议 + */ + @Schema(description = "应用协议") + private String appProtocol; + + /** + * 应用类型 + */ + @Schema(description = "应用类型") + private String appType; + + /** + * 主体ID(用户、分组、组织机构) + */ + @Schema(description = "主体ID") + private String subjectId; + + /** + * 授权主体 + */ + @Schema(description = "授权主体") + private String subjectName; + + /** + * 主体类型(用户、分组、组织机构) + */ + @Schema(description = "主体类型") + private PolicySubjectType subjectType; + + /** + * 添加时间 + */ + @Schema(description = "添加时间") + private LocalDateTime createTime; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppAccountListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppAccountListResult.java new file mode 100644 index 00000000..725902f8 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppAccountListResult.java @@ -0,0 +1,95 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.app; + +import java.time.LocalDateTime; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * AppAccountCreateParam 应用账户查询结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/24 22:13 + */ +@Data +@Schema(description = "应用账户列表查询结果") +public class AppAccountListResult { + + /** + * id + */ + @Schema(description = "id") + private String id; + + /** + * 应用ID + */ + @Schema(description = "应用ID") + private Long appId; + + /** + * 应用名称 + */ + @Schema(description = "应用名称") + private String appName; + + /** + * 模板 + */ + @Schema(description = "应用模版") + private String appTemplate; + + /** + * 协议 + */ + @Schema(description = "应用协议") + private String appProtocol; + + /** + * 应用类型 + */ + @Schema(description = "应用类型") + private String appType; + + /** + * 用户ID + */ + @Schema(description = "用户ID") + private Long userId; + + /** + * 用户名称 + */ + @Schema(description = "用户名称") + private String username; + + /** + * 账户名称 + */ + @Schema(description = "账户名称") + private String account; + + /** + * 添加时间 + */ + @Schema(description = "添加时间") + private LocalDateTime createTime; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppCertListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppCertListResult.java new file mode 100644 index 00000000..c24986f4 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppCertListResult.java @@ -0,0 +1,73 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.app; + +import cn.topiam.employee.common.enums.app.AppCertUsingType; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 获取应用证书列表结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/31 23:29 + */ +@Data +@AllArgsConstructor +@Schema(description = "获取应用证书列表结果") +public class AppCertListResult { + /** + * ID + */ + @Parameter(description = "证书ID") + private String id; + + /** + * 应用ID + */ + @Parameter(description = "应用ID") + private String appId; + + /** + * 签名算法 + */ + @Parameter(description = "签名算法") + private String signAlgo; + + /** + * 私钥长度 + */ + @Parameter(description = "私钥长度") + private Integer keyLong; + + /** + * 证书 + */ + @Parameter(description = "证书") + private String cert; + + /** + * 使用类型 + */ + @Parameter(description = "使用类型") + private AppCertUsingType usingType; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppCreateResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppCreateResult.java new file mode 100644 index 00000000..ff53e935 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppCreateResult.java @@ -0,0 +1,44 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.app; + +import java.io.Serializable; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 应用创建返回结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/26 23:25 + */ +@Data +@AllArgsConstructor +@Schema(description = "应用创建返回结果") +public class AppCreateResult implements Serializable { + + /** + * ID + */ + @Parameter(description = "ID") + private String id; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppGetResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppGetResult.java new file mode 100644 index 00000000..044077dc --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppGetResult.java @@ -0,0 +1,107 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.app; + +import java.io.Serializable; +import java.time.LocalDateTime; + +import cn.topiam.employee.common.enums.app.AppProtocol; +import cn.topiam.employee.common.enums.app.AppType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 获取应用返回 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/9/27 19:29 + */ +@Data +@Schema(description = "获取应用返回结果") +public class AppGetResult implements Serializable { + /** + * ID + */ + @Parameter(description = "ID") + private String id; + /** + * 应用名称 + */ + @Parameter(description = "应用名称") + private String name; + /** + * 客户端ID + */ + @Parameter(description = "客户端ID") + private String clientId; + /** + * 客户端秘钥 + */ + @Parameter(description = "客户端秘钥") + private String clientSecret; + /** + * 应用类型 + */ + @Parameter(description = "应用类型") + private AppType type; + + /** + * 应用图标 + */ + @Parameter(description = "应用图标") + private String icon; + + /** + * 模板 + */ + @Parameter(description = "模板") + private String template; + + /** + * 协议 + */ + @Parameter(description = "协议") + private AppProtocol protocol; + + /** + * 协议名称 + */ + @Parameter(description = "协议名称") + private String protocolName; + + /** + * 是否启用 + */ + @Parameter(description = "是否启用") + private Boolean enabled; + + /** + * 创建时间 + */ + @Parameter(description = "创建时间") + private LocalDateTime createTime; + + /** + * 备注 + */ + @Parameter(description = "备注") + private String remark; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppListResult.java new file mode 100644 index 00000000..45ed09ed --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppListResult.java @@ -0,0 +1,85 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.app; + +import java.io.Serializable; + +import cn.topiam.employee.common.enums.app.AppProtocol; +import cn.topiam.employee.common.enums.app.AppType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 应用列表返回 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/9/27 19:29 + */ +@Data +@Schema(description = "应用列表返回") +public class AppListResult implements Serializable { + /** + * ID + */ + @Parameter(description = "ID") + private String id; + /** + * 应用名称 + */ + @Parameter(description = "应用名称") + private String name; + + /** + * 应用类型 + */ + @Parameter(description = "应用类型") + private AppType type; + + /** + * 应用图标 + */ + @Parameter(description = "应用图标") + private String icon; + + /** + * 模板 + */ + @Parameter(description = "模板") + private String template; + + /** + * 协议 + */ + @Parameter(description = "协议") + private AppProtocol protocol; + + /** + * 是否启用 + */ + @Parameter(description = "是否启用") + private Boolean enabled; + + /** + * 备注 + */ + @Parameter(description = "备注") + private String remark; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionActionListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionActionListResult.java new file mode 100644 index 00000000..1555d474 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionActionListResult.java @@ -0,0 +1,131 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.app; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +import lombok.Data; +import lombok.experimental.Accessors; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 获取应用资源权限列表 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:08 + */ +@Data +@Accessors(chain = true) +@Schema(description = "获取应用资源权限列表") +public class AppPermissionActionListResult implements Serializable { + + @Serial + private static final long serialVersionUID = 3320953184046791392L; + /** + * 资源ID + */ + @Parameter(description = "资源ID") + private String id; + + /** + * 资源编码 + */ + @Parameter(description = "资源编码") + private String code; + + /** + * 资源名称 + */ + @Parameter(description = "资源名称") + private String name; + + /** + * 所属应用 + */ + @Parameter(description = "所属应用") + private String appId; + + /** + * desc + */ + @Parameter(description = "描述") + private String desc; + + /** + * 是否启用 + */ + @Parameter(description = "是否启用") + private Boolean enabled; + + /** + * 路由权限 + */ + @Parameter(description = "菜单权限") + private List menus; + + /** + * 操作权限 + */ + @Parameter(description = "操作权限") + private List buttons; + + /** + * 接口权限 + */ + @Parameter(description = "接口权限") + private List apis; + + /** + * 操作权限 + */ + @Parameter(description = "数据权限") + private List datas; + + /** + * 其他权限 + */ + @Parameter(description = "其他权限") + private List others; + + @Data + @Schema(description = "权限项") + public static class Action implements Serializable { + + /** + * 权限ID + */ + @Parameter(description = "权限ID") + private String id; + + /** + * 权限名称 + */ + @Parameter(description = "权限名称") + private String name; + + /** + * 权限标识 + */ + @Parameter(description = "权限标识") + private String access; + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionPolicyGetResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionPolicyGetResult.java new file mode 100644 index 00000000..c583ab3e --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionPolicyGetResult.java @@ -0,0 +1,87 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.app; + +import java.io.Serializable; + +import cn.topiam.employee.common.enums.PolicyEffect; +import cn.topiam.employee.common.enums.PolicyObjectType; +import cn.topiam.employee.common.enums.PolicySubjectType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 获取资源 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/26 21:45 + */ +@Schema(description = "获取资源结果") +@Data +public class AppPermissionPolicyGetResult implements Serializable { + /** + * ID + */ + @Parameter(description = "id") + private String id; + + /** + * 授权主体id + */ + @Parameter(description = "授权主体id") + private String subjectId; + + /** + * 授权主体名称 + */ + @Parameter(description = "授权主体名称") + private String subjectName; + + /** + * 权限主体类型(用户、角色、分组、组织机构) + */ + @Parameter(description = "授权主体类型") + private PolicySubjectType subjectType; + + /** + * 权限客体ID + */ + @Parameter(description = "授权客体id") + private Long objectId; + + /** + * 权限客体名菜 + */ + @Parameter(description = "授权客体名称") + private String objectName; + + /** + * 权限客体类型(权限、角色) + */ + @Parameter(description = "授权客体类型") + private PolicyObjectType objectType; + + /** + * 授权作用 + */ + @Parameter(description = "授权作用") + private PolicyEffect effect; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionPolicyListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionPolicyListResult.java new file mode 100644 index 00000000..6257458b --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionPolicyListResult.java @@ -0,0 +1,84 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.app; + +import cn.topiam.employee.common.enums.PolicyEffect; +import cn.topiam.employee.common.enums.PolicyObjectType; +import cn.topiam.employee.common.enums.PolicySubjectType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * + * @author TopIAM + * Created by support@topiam.cn on 2022/9/9 23:33 + */ +@Schema(description = "获取授权列表") +@Data +public class AppPermissionPolicyListResult { + /** + * ID + */ + @Parameter(description = "id") + private String id; + + /** + * 授权主体id + */ + @Parameter(description = "授权主体id") + private String subjectId; + + /** + * 授权主体名称 + */ + @Parameter(description = "授权主体名称") + private String subjectName; + + /** + * 权限主体类型(用户、角色、分组、组织机构) + */ + @Parameter(description = "授权主体类型") + private PolicySubjectType subjectType; + + /** + * 权限客体ID + */ + @Parameter(description = "授权客体id") + private Long objectId; + + /** + * 权限客体名菜 + */ + @Parameter(description = "授权客体名称") + private String objectName; + + /** + * 权限客体类型(权限、角色) + */ + @Parameter(description = "授权客体类型") + private PolicyObjectType objectType; + + /** + * 授权作用 + */ + @Parameter(description = "授权作用") + private PolicyEffect effect; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionResourceGetResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionResourceGetResult.java new file mode 100644 index 00000000..419cba2e --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionResourceGetResult.java @@ -0,0 +1,107 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.app; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +import cn.topiam.employee.common.enums.PermissionActionType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 获取资源 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/26 21:45 + */ +@Schema(description = "获取资源结果") +@Data +public class AppPermissionResourceGetResult implements Serializable { + /** + * 名称 + */ + @Schema(description = "资源名称") + private String name; + + /** + * 编码 + */ + @Schema(description = "资源编码") + private String code; + + /** + * 描述 + */ + @Schema(description = "资源描述") + private String desc; + + /** + * 所属应用 + */ + @Schema(description = "所属应用") + private Long appId; + + /** + * 资源权限 + */ + @Schema(description = "资源权限") + private List actions; + + /** + * AppPermissionsActionParam + * + * @author TopIAM + * Created by support@topiam.cn on 2022/9/1 00:18 + */ + @Data + public static class AppPermissionsAction implements Serializable { + + @Serial + private static final long serialVersionUID = -6391182747252245592L; + + /** + * ID + */ + @Hidden + @Schema(description = "ID") + private String id; + + /** + * 权限类型 + */ + @Schema(description = "权限类型") + private PermissionActionType type; + + /** + * 权限值 + */ + @Schema(description = "权限值") + private String value; + + /** + * 权限描述 + */ + @Schema(description = "权限描述") + private String name; + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionResourceListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionResourceListResult.java new file mode 100644 index 00000000..f084d6ea --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionResourceListResult.java @@ -0,0 +1,77 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.app; + +import java.io.Serial; +import java.io.Serializable; + +import lombok.Data; +import lombok.experimental.Accessors; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 资源分页查询结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:08 + */ +@Data +@Accessors(chain = true) +@Schema(description = "分页查询资源结果") +public class AppPermissionResourceListResult implements Serializable { + + @Serial + private static final long serialVersionUID = 3320953184046791392L; + /** + * ID + */ + @Parameter(description = "id") + private String id; + + /** + * 资源名称 + */ + @Parameter(description = "资源名称") + private String name; + + /** + * code + */ + @Parameter(description = "资源编码") + private String code; + + /** + * 所属应用 + */ + @Parameter(description = "所属应用") + private String appId; + + /** + * desc + */ + @Parameter(description = "描述") + private String desc; + + /** + * 是否启用 + */ + @Parameter(description = "是否启用") + private Boolean enabled; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionRoleListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionRoleListResult.java new file mode 100644 index 00000000..7e371caa --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionRoleListResult.java @@ -0,0 +1,75 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.app; + +import java.io.Serial; +import java.io.Serializable; + +import lombok.Data; +import lombok.experimental.Accessors; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 角色分页查询结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:08 + */ +@Data +@Accessors(chain = true) +@Schema(description = "分页查询角色结果") +public class AppPermissionRoleListResult implements Serializable { + + @Serial + private static final long serialVersionUID = 3320953184046791392L; + /** + * ID + */ + @Parameter(description = "id") + private String id; + /** + * 角色名称 + */ + @Parameter(description = "角色名称") + private String name; + + /** + * 角色编码 + */ + @Parameter(description = "角色编码") + private String code; + + /** + * 所属应用 + */ + @Parameter(description = "所属应用") + private String appId; + + /** + * 是否启用 + */ + @Parameter(description = "是否启用") + private Boolean enabled; + /** + * remark + */ + @Parameter(description = "描述") + private String remark; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionRoleResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionRoleResult.java new file mode 100644 index 00000000..8b2ab288 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppPermissionRoleResult.java @@ -0,0 +1,69 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.app; + +import java.io.Serializable; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 获取角色 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/26 21:45 + */ +@Schema(description = "获取角色") +@Data +public class AppPermissionRoleResult implements Serializable { + /** + * id + */ + @Parameter(description = "id") + private String id; + /** + * appId + */ + @Parameter(description = "应用ID") + private String appId; + /** + * 角色名称 + */ + @Parameter(description = "角色名称") + private String name; + + /** + * 角色编码 + */ + @Parameter(description = "角色编码") + private String code; + + /** + * 是否启用 + */ + @Parameter(description = "是否启用") + private Boolean enabled; + + /** + * 备注 + */ + @Parameter(description = "备注") + private String remark; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppTemplateResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppTemplateResult.java new file mode 100644 index 00000000..07b1bbe7 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/AppTemplateResult.java @@ -0,0 +1,72 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.app; + +import java.io.Serializable; + +import cn.topiam.employee.common.enums.app.AppProtocol; +import cn.topiam.employee.common.enums.app.AppType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 应用模板返回 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/9/27 19:29 + */ +@Data +@Schema(description = "应用模板") +public class AppTemplateResult implements Serializable { + /** + * code + */ + @Parameter(description = "CODE") + private String code; + + /** + * protocol + */ + @Parameter(description = "协议") + private AppProtocol protocol; + + /** + * 应用类型 + */ + @Parameter(description = "应用类型") + private AppType type; + + /** + * 名称 + */ + @Parameter(description = "名称") + private String name; + /** + * 图标 + */ + @Parameter(description = "图标") + private String icon; + /** + * 描述 + */ + @Parameter(description = "描述") + private String desc; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/ParseSaml2MetadataResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/ParseSaml2MetadataResult.java new file mode 100644 index 00000000..094f9e72 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/ParseSaml2MetadataResult.java @@ -0,0 +1,97 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.app; + +import java.io.Serializable; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 解析SAML2 元数据 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/27 23:12 + */ +@Data +@Schema(description = "解析 SAML2 元数据返回值") +public class ParseSaml2MetadataResult implements Serializable { + + /** + * EntityId + */ + @Parameter(description = "EntityId") + private String entityId; + + /** + * 签名断言 + */ + @Parameter(description = "是否对断言签名") + private Boolean wantAssertionsSigned; + + /** + * 请求签名 + */ + @Parameter(description = "是否对SAML Request签名进行验证") + private Boolean authnRequestsSigned; + + /** + * ssoAcsUrl + */ + @Parameter(description = "SSO ACS 地址") + private String acsUrl; + + /** + * defaultNameIdFormat + */ + @Parameter(description = "默认 NameId 格式") + private String defaultNameIdFormat; + + /** + * binding + */ + @Parameter(description = "默认 ACS 绑定方式") + private String defaultAcsBinding; + + /** + * 签名证书 + */ + @Parameter(description = "签名证书") + private String signCert; + + /** + * 单点登出启用 + */ + @Parameter(description = "单点登出是否启用") + private Boolean sloEnabled; + + /** + * 单点登出地址 + */ + @Parameter(description = "单点登出地址") + private String slsUrl; + + /** + * 单点登出绑定模式 + */ + @Parameter(description = "单点登出绑定模式") + private String slsBinding; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/UserIdpBindListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/UserIdpBindListResult.java new file mode 100644 index 00000000..76039eef --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/app/UserIdpBindListResult.java @@ -0,0 +1,67 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.app; + +import java.time.LocalDateTime; + +import cn.topiam.employee.common.enums.IdentityProviderType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 用户身份提供商绑定列表查询结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/11/3 22:13 + */ +@Data +@Schema(description = "用户身份提供商绑定列表查询结果") +public class UserIdpBindListResult { + + /** + * id + */ + @Schema(description = "id") + private String id; + + /** + * open id + */ + @Schema(description = "open id") + private Long openId; + + /** + * 提供商名称 + */ + @Schema(description = "提供商名称") + private String idpName; + + /** + * 提供商类型 + */ + @Schema(description = "提供商类型") + private IdentityProviderType idpType; + + /** + * 绑定时间 + */ + @Schema(description = "绑定时间") + private LocalDateTime bindTime; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/authentication/IdentityProviderCreateResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/authentication/IdentityProviderCreateResult.java new file mode 100644 index 00000000..b48c7e96 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/authentication/IdentityProviderCreateResult.java @@ -0,0 +1,52 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.authentication; + +import java.io.Serializable; + +import cn.topiam.employee.common.enums.IdentityProviderType; + +import lombok.Builder; +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 身份源创建返回 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/3/29 21:43 + */ +@Data +@Builder +@Schema(description = "身份源创建返回") +public class IdentityProviderCreateResult implements Serializable { + + /** + * ID + */ + @Parameter(description = "ID") + private String id; + + /** + * 提供商类型 + */ + @Parameter(description = "提供商类型") + private IdentityProviderType type; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/authentication/IdentityProviderListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/authentication/IdentityProviderListResult.java new file mode 100644 index 00000000..7bfc6f53 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/authentication/IdentityProviderListResult.java @@ -0,0 +1,74 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.authentication; + +import java.io.Serializable; + +import cn.topiam.employee.common.enums.IdentityProviderType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 社交认证源平台列表,带有元素字段,避免前端重复画页面,基本都是input + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/26 19:57 + */ +@Data +@Schema(description = "社交认证源平台列表") +public class IdentityProviderListResult implements Serializable { + + /** + * ID + */ + @Parameter(description = "ID") + private String id; + + /** + * name + */ + @Parameter(description = "名称") + private String name; + + /** + * 提供商 + */ + @Parameter(description = "提供商") + private IdentityProviderType type; + + /** + * 是否启用 + */ + @Parameter(description = "是否启用") + private Boolean enabled; + + /** + * 描述 + */ + @Parameter(description = "描述") + private String desc; + + /** + * 备注 + */ + @Parameter(description = "备注") + private String remark; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/authentication/IdentityProviderResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/authentication/IdentityProviderResult.java new file mode 100644 index 00000000..f3a8d575 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/authentication/IdentityProviderResult.java @@ -0,0 +1,84 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.authentication; + +import java.io.Serial; +import java.io.Serializable; + +import cn.topiam.employee.authentication.common.config.IdentityProviderConfig; +import cn.topiam.employee.common.enums.IdentityProviderType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 认证源详情 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/21 21:21 + */ +@Data +@Schema(description = "获取社交认证源") +public class IdentityProviderResult implements Serializable { + @Serial + private static final long serialVersionUID = -1440230086940289961L; + + /** + * ID + */ + @Parameter(description = "ID") + private String id; + + /** + * 名称 + */ + @Parameter(description = "名称") + private String name; + + /** + * 提供商类型 + */ + @Parameter(description = "提供商类型") + private IdentityProviderType type; + + /** + * 配置 + */ + @Parameter(description = "配置JSON") + private IdentityProviderConfig config; + + /** + * 是否展示 + */ + @Parameter(description = "是否展示") + private Boolean displayed; + + /** + * 备注 + */ + @Parameter(description = "备注") + private String remark; + + /** + * 回调地址 + */ + @Parameter(description = "回调地址") + private String redirectUri; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceConfigGetResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceConfigGetResult.java new file mode 100644 index 00000000..b8747621 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceConfigGetResult.java @@ -0,0 +1,70 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.identitysource; + +import java.io.Serial; +import java.io.Serializable; + +import cn.topiam.employee.common.entity.identitysource.config.JobConfig; +import cn.topiam.employee.common.entity.identitysource.config.StrategyConfig; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 获取身份源配置 + * + * @author TopIAM + */ +@Data +@Schema(description = "获取身份源配置") +public class IdentitySourceConfigGetResult implements Serializable { + @Serial + private static final long serialVersionUID = -1440230086940289961L; + /** + * ID + */ + @Parameter(description = "ID") + private String id; + + /** + * 是否已配置 + */ + @Parameter(description = "是否已配置") + private Boolean configured; + + /** + * 基础配置 + */ + @Parameter(description = "基础配置") + private Object basicConfig; + + /** + * 策略配置 + */ + @Parameter(description = "策略配置") + private StrategyConfig strategyConfig; + + /** + * 任务配置 + */ + @Parameter(description = "任务配置") + private JobConfig jobConfig; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceEventRecordListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceEventRecordListResult.java new file mode 100644 index 00000000..9bcea0d5 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceEventRecordListResult.java @@ -0,0 +1,86 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.identitysource; + +import java.io.Serializable; +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import cn.topiam.employee.common.enums.SyncStatus; +import cn.topiam.employee.common.enums.identitysource.IdentitySourceActionType; +import cn.topiam.employee.common.enums.identitysource.IdentitySourceObjectType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import static cn.topiam.employee.support.constant.EiamConstants.DEFAULT_DATE_TIME_FORMATTER_PATTERN; + +/** + * 身份源事件记录列表 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/25 23:05 + */ +@Data +@Schema(description = "身份源事件记录列表") +public class IdentitySourceEventRecordListResult implements Serializable { + /** + * 唯一标志 + */ + @Parameter(description = "ID") + private String id; + + /** + * 动作类型 + */ + @Parameter(description = "动作类型") + private IdentitySourceActionType actionType; + + /** + * 对象ID + */ + @Parameter(description = "对象ID") + private String objectId; + + /** + * 对象名称 + */ + @Parameter(description = "对象名称") + private String objectName; + + /** + * 对象类型 + */ + @Parameter(description = "对象类型") + private IdentitySourceObjectType objectType; + + /** + * 事件时间 + */ + @Parameter(description = "事件时间") + @JsonFormat(pattern = DEFAULT_DATE_TIME_FORMATTER_PATTERN) + private LocalDateTime eventTime; + + /** + * 事件状态 + */ + @Parameter(description = "事件状态") + private SyncStatus status; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceGetResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceGetResult.java new file mode 100644 index 00000000..94bbb527 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceGetResult.java @@ -0,0 +1,73 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.identitysource; + +import java.io.Serial; +import java.io.Serializable; + +import cn.topiam.employee.common.enums.identityprovider.IdentitySourceProvider; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 身份源源详情 + * + * @author TopIAM + */ +@Data +@Schema(description = "身份源源详情") +public class IdentitySourceGetResult implements Serializable { + @Serial + private static final long serialVersionUID = -1440230086940289961L; + /** + * ID + */ + @Parameter(description = "ID") + private String id; + /** + * 名称 + */ + @Parameter(description = "名称") + private String name; + /** + * 平台 + */ + @Parameter(description = "平台") + private IdentitySourceProvider provider; + + /** + * 是否启用 + */ + @Parameter(description = "是否启用") + private Boolean enabled; + + /** + * 是否已配置 + */ + @Parameter(description = "是否已配置") + private Boolean configured; + + /** + * 备注 + */ + @Parameter(description = "备注") + private String remark; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceListResult.java new file mode 100644 index 00000000..53827644 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceListResult.java @@ -0,0 +1,77 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.identitysource; + +import java.io.Serializable; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 身份源列表 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/25 23:05 + */ +@Data +@Schema(description = "身份源列表") +public class IdentitySourceListResult implements Serializable { + /** + * 唯一标志 + */ + @Parameter(description = "ID") + private String id; + + /** + * 名称 + */ + @Parameter(description = "名称") + private String name; + + /** + * ICON + */ + @Parameter(description = "图标") + private String icon; + + /** + * 描述 + */ + @Parameter(description = "描述") + private String desc; + + /** + * 提供商 + */ + @Parameter(description = "提供商") + private String provider; + + /** + * 备注 + */ + @Parameter(description = "备注") + private String remark; + + /** + * 是否启用 + */ + @Parameter(description = "是否启用") + private Boolean enabled; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceSyncHistoryListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceSyncHistoryListResult.java new file mode 100644 index 00000000..e616da4f --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceSyncHistoryListResult.java @@ -0,0 +1,122 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.identitysource; + +import java.io.Serializable; +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import cn.topiam.employee.common.enums.TriggerType; +import cn.topiam.employee.common.enums.identitysource.IdentitySourceObjectType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import static cn.topiam.employee.support.constant.EiamConstants.DEFAULT_DATE_TIME_FORMATTER_PATTERN; + +/** + * 身份源同步记录详情列表 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/25 23:05 + */ +@Data +@Schema(description = "身份源同步历史列表") +public class IdentitySourceSyncHistoryListResult implements Serializable { + /** + * 唯一标志 + */ + @Parameter(description = "ID") + private String id; + + /** + * 批号 + */ + @Parameter(description = "批号") + private String batch; + + /** + * 身份源ID + */ + @Parameter(description = "身份源ID") + private String identitySourceId; + + /** + * 创建数量 + */ + @Parameter(description = "创建数量") + private String createdCount; + + /** + * 更新数量 + */ + @Parameter(description = "更新数量") + private String updatedCount; + + /** + * 删除数量 + */ + @Parameter(description = "删除数量") + private String deletedCount; + + /** + * 跳过数量 + */ + @Parameter(description = "跳过数量") + private String skippedCount; + + /** + * 开始时间 + */ + @Parameter(description = "开始时间") + @JsonFormat(pattern = DEFAULT_DATE_TIME_FORMATTER_PATTERN) + private LocalDateTime startTime; + + /** + * 结束时间 + */ + @Parameter(description = "结束时间") + @JsonFormat(pattern = DEFAULT_DATE_TIME_FORMATTER_PATTERN) + private LocalDateTime endTime; + + /** + * 耗时 + */ + @Parameter(description = "耗时") + private String spendTime; + + /** + * 对象类型(用户、组织) + */ + @Parameter(description = "对象类型") + private IdentitySourceObjectType objectType; + + /** + * 触发类型(手动、任务、事件) + */ + @Parameter(description = "触发类型") + private TriggerType triggerType; + + /** + * 同步状态 + */ + @Parameter(description = "同步状态") + private String status; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceSyncRecordListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceSyncRecordListResult.java new file mode 100644 index 00000000..74ae1b67 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/identitysource/IdentitySourceSyncRecordListResult.java @@ -0,0 +1,80 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.identitysource; + +import java.io.Serializable; + +import cn.topiam.employee.common.enums.identitysource.IdentitySourceActionType; +import cn.topiam.employee.common.enums.identitysource.IdentitySourceObjectType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 身份源同步详情列表 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/25 23:05 + */ +@Data +@Schema(description = "身份源同步记录列表") +public class IdentitySourceSyncRecordListResult implements Serializable { + /** + * ID + */ + @Parameter(description = "ID") + private String id; + + /** + * 动作类型 + */ + @Parameter(description = "动作类型") + private IdentitySourceActionType actionType; + + /** + * 对象ID + */ + @Parameter(description = "对象ID") + private String objectId; + + /** + * 对象名称 + */ + @Parameter(description = "对象名称") + private String objectName; + + /** + * 对象类型 + */ + @Parameter(description = "对象类型") + private IdentitySourceObjectType objectType; + + /** + * 状态 + */ + @Parameter(description = "状态") + private String status; + + /** + * 描述 + */ + @Parameter(description = "描述") + private String desc; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/package-info.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/package-info.java new file mode 100644 index 00000000..88580f78 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result; \ No newline at end of file diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/AdministratorListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/AdministratorListResult.java new file mode 100644 index 00000000..a9fdb8cf --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/AdministratorListResult.java @@ -0,0 +1,103 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.setting; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import lombok.Data; +import lombok.experimental.Accessors; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import static cn.topiam.employee.support.constant.EiamConstants.DEFAULT_DATE_TIME_FORMATTER_PATTERN; + +/** + * 查询权限列表结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:08 + */ +@Data +@Accessors(chain = true) +@Schema(description = "查询权限列表结果") +public class AdministratorListResult implements Serializable { + + @Serial + private static final long serialVersionUID = 3320953184046791392L; + /** + * ID + */ + @Parameter(description = "ID") + private String id; + /** + * 用户名 + */ + @Parameter(description = "用户名") + private String username; + /** + * 邮箱 + */ + @Parameter(description = "邮箱") + private String email; + + /** + * 手机号 + */ + @Parameter(description = "手机号") + private String phone; + + /** + * 头像URL + */ + @Parameter(description = "头像URL") + private String avatar; + + /** + * 状态 ENABLE:启用 DISABLE:禁用 LOCKING:锁定 + */ + @Parameter(description = "状态") + private String status; + + /** + * 邮箱验证有效 + */ + @Parameter(description = "邮箱验证有效") + private Boolean emailVerified; + + /** + * 认证次数 + */ + @Parameter(description = "认证次数") + private Long authTotal; + + /** + * 上次认证IP + */ + @Parameter(description = "上次认证IP") + private String lastAuthIp; + /** + * 上次认证时间 + */ + @JsonFormat(pattern = DEFAULT_DATE_TIME_FORMATTER_PATTERN) + @Parameter(description = "上次认证时间") + private LocalDateTime lastAuthTime; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/AdministratorResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/AdministratorResult.java new file mode 100644 index 00000000..35461595 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/AdministratorResult.java @@ -0,0 +1,97 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.setting; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 管理员详情 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/26 21:45 + */ +@Schema(description = "根据ID获取管理员") +@Data +public class AdministratorResult implements Serializable { + + @Serial + private static final long serialVersionUID = 2578080414501381607L; + /** + * ID + */ + @Parameter(description = "ID") + private String id; + /** + * 用户名 + */ + @Parameter(description = "用户名") + private String username; + /** + * 邮箱 + */ + @Parameter(description = "邮箱") + private String email; + + /** + * 手机号 + */ + @Parameter(description = "手机号") + private String phone; + + /** + * 头像URL + */ + @Parameter(description = "头像URL") + private String avatar; + + /** + * 状态 ENABLE:启用 DISABLE:禁用 LOCKING:锁定 + */ + @Parameter(description = "状态") + private String status; + + /** + * 邮箱验证有效 + */ + @Parameter(description = "邮箱验证有效") + private Boolean emailVerified; + + /** + * 认证次数 + */ + @Parameter(description = "认证次数") + private Long authTotal; + + /** + * 上次认证IP + */ + @Parameter(description = "上次认证IP") + private String lastAuthIp; + /** + * 上次认证时间 + */ + @Parameter(description = "上次认证时间") + private LocalDateTime lastAuthTime; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/EmailProviderConfigResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/EmailProviderConfigResult.java new file mode 100644 index 00000000..1ae77ccc --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/EmailProviderConfigResult.java @@ -0,0 +1,96 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.setting; + +import java.io.Serial; +import java.io.Serializable; + +import cn.topiam.employee.common.message.enums.MailProvider; +import cn.topiam.employee.common.message.enums.MailSafetyType; + +import lombok.Builder; +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 邮件服务商配置查询结果 + * + * @author TopIAM + */ +@Data +@Builder +@Schema(description = "邮件服务商配置查询结果") +public class EmailProviderConfigResult implements Serializable { + @Serial + private static final long serialVersionUID = 8584300384703986791L; + + /** + * smtp地址 + */ + @Parameter(description = "smtp地址") + private String smtpUrl; + + /** + * 端口 + */ + @Parameter(description = "端口") + private Integer port; + + /** + * 安全验证 + */ + @Parameter(description = "安全验证") + private MailSafetyType safetyType; + + /** + * 用户名 + */ + @Parameter(description = "用户名") + private String username; + + /** + * 秘钥 + */ + @Parameter(description = "秘钥") + private String secret; + + /** + * 配置JSON串 + */ + @Parameter(description = "配置JSON串") + private String config; + + /** + * 平台 + */ + @Parameter(description = "平台") + private MailProvider provider; + + /** + * 描述 + */ + @Parameter(description = "描述") + private String desc; + /** + * 是否启用 + */ + @Parameter(description = "是否启用") + private Boolean enabled; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/EmailTemplateListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/EmailTemplateListResult.java new file mode 100644 index 00000000..745fa773 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/EmailTemplateListResult.java @@ -0,0 +1,64 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.setting; + +import java.io.Serial; +import java.io.Serializable; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 邮件模板类型返回值 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/19 22:09 + */ +@Schema(description = "邮件模板类列表返回值") +@Data +public class EmailTemplateListResult implements Serializable { + @Serial + private static final long serialVersionUID = 6499437680155500022L; + /** + * 名称 + */ + @Parameter(description = "名称") + private String name; + /** + * 编码 + */ + @Parameter(description = "编码") + private String code; + /** + * 自定义 + */ + @Parameter(description = "自定义") + private Boolean custom; + /** + * 描述 + */ + @Parameter(description = "详情") + private String description; + /** + * 内容 + */ + @Parameter(description = "内容") + private String content; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/EmailTemplateResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/EmailTemplateResult.java new file mode 100644 index 00000000..00c76460 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/EmailTemplateResult.java @@ -0,0 +1,74 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.setting; + +import java.io.Serial; +import java.io.Serializable; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 邮件模板配置结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/17 23:05 + */ +@Data +@Schema(description = "获取邮件模板") +public class EmailTemplateResult implements Serializable { + @Serial + private static final long serialVersionUID = 6499437680155500022L; + /** + * 名称 + */ + @Parameter(description = "名称") + private String name; + /** + * 发送人 + */ + @Parameter(description = "发送人") + private String sender; + + /** + * 主题 + */ + @Parameter(description = "主题") + private String subject; + + /** + * 内容 + */ + @Parameter(description = "内容") + private String content; + + /** + * 描述 + */ + @Parameter(description = "描述") + private String desc; + + /** + * 自定义 + */ + @Parameter(description = "自定义") + private Boolean custom; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/GeoIpProviderResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/GeoIpProviderResult.java new file mode 100644 index 00000000..54ddef4b --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/GeoIpProviderResult.java @@ -0,0 +1,63 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.setting; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotNull; + +import cn.topiam.employee.common.geo.GeoLocationProviderConfig; +import cn.topiam.employee.common.geo.maxmind.enums.GeoLocationProvider; + +import lombok.Builder; +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 获取地理位置服务商配置信息 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 21:27 + */ +@Data +@Builder +@Schema(description = "获取地理位置服务商配置信息") +public class GeoIpProviderResult implements Serializable { + @Serial + private static final long serialVersionUID = -6723117700517052520L; + /** + * 地理位置服务商 + */ + @Schema(description = "地理位置提供商") + @NotNull(message = "地理位置提供商不能为空") + private GeoLocationProvider provider; + + /** + * 配置信息 + */ + @Schema(description = "配置信息") + private GeoLocationProviderConfig.GeoLocationConfig config; + /** + * 是否启用 + */ + @Schema(description = "是否启用") + private Boolean enabled; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/PasswordPolicyConfigResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/PasswordPolicyConfigResult.java new file mode 100644 index 00000000..92dd74e6 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/PasswordPolicyConfigResult.java @@ -0,0 +1,107 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.setting; + +import java.io.Serializable; + +import cn.topiam.employee.core.security.password.enums.PasswordComplexityRule; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 设置密码策略配置结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/10/4 19:48 + */ +@Data +public class PasswordPolicyConfigResult implements Serializable { + /** + * 密码最大长度 + */ + @Schema(description = "密码最大长度") + private Integer passwordBiggestLength; + + /** + * 密码最小长度 + */ + @Schema(description = "密码最小长度") + private Integer passwordLeastLength; + + /** + * 复杂度 + */ + @Schema(description = "密码复杂度") + private PasswordComplexityRule passwordComplexity; + + /** + * 弱密码库 + */ + @Schema(description = "自定义弱密码") + private String customWeakPassword; + + /** + * 弱密码检查 + */ + @Schema(description = "弱密码检查") + private Boolean weakPasswordCheck; + + /** + * 账户信息检查 + */ + @Schema(description = "账户信息检查") + private Boolean includeAccountCheck; + + /** + * 不能多少个以上相同字符 + */ + @Schema(description = "不能多少个以上相同字符") + private Integer notSameChars; + + /** + * 历史密码检查 + */ + @Schema(description = "历史密码检查") + private Boolean historyPasswordCheck; + + /** + * 历史密码检查次数 + */ + @Schema(description = "历史密码检查次数") + private Integer historyPasswordCheckCount; + + /** + * 非法序列检查 + */ + @Schema(description = "非法序列检查") + private Boolean illegalSequenceCheck; + + /** + * 密码有效天数 + */ + @Schema(description = "密码有效天数") + private Integer passwordValidDays; + + /** + * 密码过期前提醒天数 + */ + @Schema(description = "密码有效提醒天数") + private Integer passwordValidWarnBeforeDays; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/SecurityBasicConfigResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/SecurityBasicConfigResult.java new file mode 100644 index 00000000..979516b9 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/SecurityBasicConfigResult.java @@ -0,0 +1,77 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.setting; + +import java.io.Serializable; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 安全高级配置结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/10/4 21:58 + */ +@Data +@Schema(description = "安全高级配置结果") +public class SecurityBasicConfigResult implements Serializable { + /** + * 会话有效时间 + */ + @Parameter(description = "会话有效时间(秒)") + private Integer sessionValidTime; + + /** + * 验证码有效时间 + */ + @Parameter(description = "验证码有效时间(秒)") + private Integer verifyCodeValidTime; + + /** + * 记住我有效时间 + */ + @Schema(description = "记住我有效时间(秒)") + private Integer rememberMeValidTime; + + /** + * 用户并发数 + */ + @Parameter(description = "用户并发数") + private Integer sessionMaximum; + + /** + * 连续登录失败持续时间 + */ + @Parameter(description = "连续登录失败持续时间") + private Integer loginFailureDuration; + + /** + * 连续登录失败次数 + */ + @Parameter(description = "连续登录失败次数") + private Integer loginFailureCount; + + /** + * 自动解锁时间(分) + */ + @Parameter(description = "自动解锁时间(分)") + private Integer autoUnlockTime; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/SecurityCaptchaConfigResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/SecurityCaptchaConfigResult.java new file mode 100644 index 00000000..d10f24db --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/SecurityCaptchaConfigResult.java @@ -0,0 +1,56 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.setting; + +import java.io.Serializable; + +import cn.topiam.employee.common.enums.CaptchaProviderType; +import cn.topiam.employee.core.security.captcha.CaptchaProviderConfig; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 行为验证码配置结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/10/4 21:58 + */ +@Data +@Schema(description = "行为验证码配置结果") +public class SecurityCaptchaConfigResult implements Serializable { + /** + * 验证码提供商 + */ + @Parameter(description = "验证码提供商") + private CaptchaProviderType provider; + + /** + * 验证码配置 + */ + @Parameter(description = "验证码配置") + private CaptchaProviderConfig config; + + /** + * 已启用 + */ + @Parameter(description = "已启用") + private Boolean enabled; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/SecurityMfaConfigResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/SecurityMfaConfigResult.java new file mode 100644 index 00000000..e996f107 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/SecurityMfaConfigResult.java @@ -0,0 +1,51 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.setting; + +import java.io.Serializable; +import java.util.List; + +import cn.topiam.employee.common.enums.MfaFactor; +import cn.topiam.employee.common.enums.MfaMode; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 安全MFA配置结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/10/4 21:58 + */ +@Data +@Schema(description = "安全MFA配置结果") +public class SecurityMfaConfigResult implements Serializable { + /** + * mode + */ + @Parameter(description = "MFA 模式") + private MfaMode mode; + + /** + * manner + */ + @Parameter(description = "MFA 因素") + private List factors; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/SmsTemplateListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/SmsTemplateListResult.java new file mode 100644 index 00000000..a2eca67e --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/SmsTemplateListResult.java @@ -0,0 +1,73 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.setting; + +import java.io.Serial; +import java.io.Serializable; + +import cn.topiam.employee.common.enums.Language; +import cn.topiam.employee.common.enums.MessageCategory; +import cn.topiam.employee.common.enums.SmsType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 短信配置结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/17 23:05 + */ +@Data +@Schema(description = "获取短信模板列表") +public class SmsTemplateListResult implements Serializable { + + @Serial + private static final long serialVersionUID = 5983857137670090984L; + + /** + * 名称 + */ + @Schema(description = "名称") + private String name; + + /** + * 类型 + */ + @Schema(description = "类型") + private SmsType type; + + /** + * 模板类型 + */ + @Schema(description = "模板类型") + private MessageCategory category; + + /** + * 内容 + */ + @Schema(description = "内容") + private String content; + + /** + * Language + */ + @Schema(description = "Language") + private Language language; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/StorageProviderConfigResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/StorageProviderConfigResult.java new file mode 100644 index 00000000..fb997c0d --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/StorageProviderConfigResult.java @@ -0,0 +1,61 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.setting; + +import java.io.Serial; +import java.io.Serializable; + +import cn.topiam.employee.common.storage.StorageConfig; +import cn.topiam.employee.common.storage.enums.StorageProvider; + +import lombok.Builder; +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 存储配置查询结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/1 23:40 + */ +@Data +@Builder +@Schema(description = "存储配置查询结果") +public class StorageProviderConfigResult implements Serializable { + + @Serial + private static final long serialVersionUID = -2667374916357438335L; + /** + * 服务商 + */ + @Parameter(description = "服务商") + private StorageProvider provider; + /** + * 启用 + */ + @Parameter(description = "是否启用") + private Boolean enabled; + /** + * 配置信息 + */ + @Parameter(description = "配置信息") + private StorageConfig config; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/WeakPasswordLibListResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/WeakPasswordLibListResult.java new file mode 100644 index 00000000..d8bb7324 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/result/setting/WeakPasswordLibListResult.java @@ -0,0 +1,43 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.result.setting; + +import java.io.Serializable; + +import lombok.Data; +import lombok.RequiredArgsConstructor; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 弱密码列表查询结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/10/11 19:19 + */ +@Data +@RequiredArgsConstructor +@Schema(description = "弱密码列表查询结果") +public class WeakPasswordLibListResult implements Serializable { + /** + * value + */ + @Parameter(description = "value") + private final String value; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/account/OrganizationCreateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/account/OrganizationCreateParam.java new file mode 100644 index 00000000..132b0924 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/account/OrganizationCreateParam.java @@ -0,0 +1,101 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.account; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import cn.topiam.employee.common.enums.OrganizationType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 创建组织架构入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:16 + */ +@Data +@Schema(description = "创建组织架构入参") +public class OrganizationCreateParam implements Serializable { + @Serial + private static final long serialVersionUID = 3118058164024117164L; + + /** + * code + */ + @Schema(description = "编码") + private String code; + + /** + * 上级部门 + */ + @NotEmpty(message = "请选择上级组织") + @Schema(description = "上级组织") + private String parentId; + + /** + * 名称 + */ + @Schema(description = "架构名称") + @NotBlank(message = "名称不能为空") + private String name; + + /** + * 类型 + */ + @Schema(description = "架构类型") + @NotNull(message = "类型不能为空") + private OrganizationType type; + + /** + * 外部ID + */ + @Schema(description = "外部ID") + private String externalId; + + /** + * 区域 + */ + @Schema(description = "所在区域") + private String area; + + /** + * 描述 + */ + @Schema(description = "架构描述") + private String desc; + + /** + * 排序 + */ + @Schema(description = "架构排序") + private String order; + + /** + * 是否启用 + */ + @Schema(description = "是否启用") + private Boolean enabled; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/account/UserCreateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/account/UserCreateParam.java new file mode 100644 index 00000000..5485396a --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/account/UserCreateParam.java @@ -0,0 +1,128 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.account; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDate; +import java.util.List; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +import cn.topiam.employee.common.enums.ListEnumDeserializer; +import cn.topiam.employee.common.enums.MessageNoticeChannel; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 用户创建请求入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:16 + */ +@Data +@Schema(description = "创建用户入参") +public class UserCreateParam implements Serializable { + @Serial + private static final long serialVersionUID = -6044649488381303849L; + /** + * 组织机构 + */ + @Schema(description = "组织机构") + @NotBlank(message = "组织机构不能为空") + private String organizationId; + + /** + * 用户名 + */ + @Schema(description = "用户名") + @NotBlank(message = "用户名不能为空") + private String username; + + /** + * 密码 + */ + @Schema(description = "登录密码") + @NotBlank(message = "登录密码不能为空") + private String password; + + /** + * 邮箱 + */ + @Email + @Schema(description = "邮箱") + private String email; + + /** + * 手机号 + */ + @Schema(description = "手机号") + private String phone; + + /** + * 姓名 + */ + @NotBlank(message = "姓名不能为空") + @Schema(description = "姓名") + private String fullName; + + /** + * 昵称 + */ + @Schema(description = "昵称") + private String nickName; + + /** + * 备注 + */ + @Schema(description = "备注") + private String remark; + + /** + * 过期日期 + */ + @Schema(description = "过期日期") + private LocalDate expireDate; + + /** + * 密码初始化配置 + */ + @Schema(description = "密码初始化配置") + private PasswordInitializeConfig passwordInitializeConfig; + + @Data + public static class PasswordInitializeConfig implements Serializable { + /** + * 启用通知 + */ + @Schema(description = "启用通知") + private Boolean enableNotice; + + /** + * 消息类型 + */ + @Schema(description = "消息类型") + @JsonDeserialize(using = ListEnumDeserializer.class) + private List noticeChannels; + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/account/UserGroupCreateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/account/UserGroupCreateParam.java new file mode 100644 index 00000000..b11e9a42 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/account/UserGroupCreateParam.java @@ -0,0 +1,59 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.account; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotBlank; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 用户创建请求入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:16 + */ +@Data +@Schema(description = "创建用户分组入参") +public class UserGroupCreateParam implements Serializable { + @Serial + private static final long serialVersionUID = -6044649488381303849L; + /** + * 用户组名称 + */ + @Schema(description = "用户组名称") + @NotBlank(message = "用户组名称不能为空") + private String name; + + /** + * 用户组编码 + */ + @Schema(description = "用户组编码") + @NotBlank(message = "用户组编码不能为空") + private String code; + + /** + * 备注 + */ + @Schema(description = "备注") + private String remark; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppAccessPolicyCreateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppAccessPolicyCreateParam.java new file mode 100644 index 00000000..15818d09 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppAccessPolicyCreateParam.java @@ -0,0 +1,60 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.app; + +import java.util.List; + +import javax.validation.constraints.NotNull; + +import cn.topiam.employee.common.enums.PolicySubjectType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 应用访问授权策略添加参数 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/9/27 19:29 + */ +@Data +@Schema(description = "应用访问授权策略添加参数") +public class AppAccessPolicyCreateParam { + + /** + * 应用ID + */ + @Schema(description = "应用ID") + @NotNull(message = "应用ID不能为空") + private String appId; + + /** + * 主体ID(用户、分组、组织机构) + */ + @Schema(description = "主体") + @NotNull(message = "主体不能为空") + private List subjectIds; + + /** + * 主体类型(用户、分组、组织机构) + */ + @Schema(description = "主体类型") + @NotNull(message = "主体类型不能为空") + private PolicySubjectType subjectType; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppAccountCreateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppAccountCreateParam.java new file mode 100644 index 00000000..ede8d543 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppAccountCreateParam.java @@ -0,0 +1,57 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.app; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * AppAccountCreateParam 应用账户新增入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/24 22:13 + */ +@Data +@Schema(description = "应用账户新增入参") +public class AppAccountCreateParam { + + /** + * 应用ID + */ + @Schema(description = "应用ID") + @NotNull(message = "应用ID不能为空") + private Long appId; + + /** + * 用户ID + */ + @Schema(description = "用户ID") + @NotNull(message = "用户ID不能为空") + private Long userId; + + /** + * 账户名称 + */ + @Schema(description = "账户名称") + @NotBlank(message = "账户名称不能为空") + private String account; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppCreateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppCreateParam.java new file mode 100644 index 00000000..efe6a88a --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppCreateParam.java @@ -0,0 +1,58 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.app; + +import java.io.Serializable; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 应用保存入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/7/18 23:26 + */ +@Data +@Schema(description = "应用保存入参") +public class AppCreateParam implements Serializable { + + /** + * 应用名称 + */ + @NotBlank(message = "应用名称不能为空") + @Schema(description = "应用名称") + private String name; + + /** + * 应用模版 + */ + @NotNull(message = "应用模版不能为空") + @Schema(description = "应用模版") + private String template; + + /** + * 备注 + */ + @Schema(description = "备注") + private String remark; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppPermissionPolicyCreateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppPermissionPolicyCreateParam.java new file mode 100644 index 00000000..79db9a06 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppPermissionPolicyCreateParam.java @@ -0,0 +1,84 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.app; + +import java.io.Serializable; + +import javax.validation.constraints.NotNull; + +import cn.topiam.employee.common.enums.PolicyEffect; +import cn.topiam.employee.common.enums.PolicyObjectType; +import cn.topiam.employee.common.enums.PolicySubjectType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 创建策略入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/26 21:46 + */ +@Data +@Schema(description = "创建策略入参") +public class AppPermissionPolicyCreateParam implements Serializable { + + /** + * 所属应用 + */ + @NotNull(message = "资源所属应用不能为空") + @Parameter(description = "所属应用") + private Long appId; + + /** + * 授权主体id + */ + @NotNull(message = "授权主体id不能为空") + @Parameter(description = "授权主体id") + private String subjectId; + + /** + * 权限主体类型(用户、角色、分组、组织机构) + */ + @NotNull(message = "授权主体类型不能为空") + @Parameter(description = "授权主体类型") + private PolicySubjectType subjectType; + + /** + * 权限客体ID + */ + @NotNull(message = "权限客体ID不能为空") + @Parameter(description = "授权客体id") + private Long objectId; + + /** + * 权限客体类型(权限、角色) + */ + @NotNull(message = "权限客体类型不能为空") + @Parameter(description = "授权客体类型") + private PolicyObjectType objectType; + + /** + * 授权作用 + */ + @NotNull(message = "授权作用不能为空") + @Parameter(description = "授权作用") + private PolicyEffect effect; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppPermissionResourceCreateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppPermissionResourceCreateParam.java new file mode 100644 index 00000000..7b2cc6bc --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppPermissionResourceCreateParam.java @@ -0,0 +1,76 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.app; + +import java.io.Serializable; +import java.util.List; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 资源创建参数 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/26 21:46 + */ +@Data +@Schema(description = "创建资源入参") +public class AppPermissionResourceCreateParam implements Serializable { + /** + * 编码 + */ + @Schema(description = "资源编码") + @NotBlank(message = "资源编码不能为空") + private String code; + /** + * 名称 + */ + @Schema(description = "资源名称") + @NotBlank(message = "资源名称不能为空") + private String name; + /** + * 描述 + */ + @Schema(description = "资源描述") + @NotBlank(message = "资源描述不能为空") + private String desc; + + /** + * 是否启用 + */ + private Boolean enabled = true; + + /** + * 所属应用 + */ + @Schema(description = "所属应用") + @NotNull(message = "所属应用不能为空") + private Long appId; + + /** + * 资源权限 + */ + @Schema(description = "资源权限") + @NotNull(message = "资源权限不能为空") + private List actions; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppPermissionRoleCreateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppPermissionRoleCreateParam.java new file mode 100644 index 00000000..f1b75aec --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppPermissionRoleCreateParam.java @@ -0,0 +1,62 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.app; + +import java.io.Serializable; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 角色创建参数 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/26 21:46 + */ +@Data +@Schema(description = "创建角色入参") +public class AppPermissionRoleCreateParam implements Serializable { + /** + * 角色名称 + */ + @NotBlank(message = "角色名称不能为空") + private String name; + /** + * 角色编码 + */ + @NotBlank(message = "角色编码不能为空") + private String code; + /** + * 启用 + */ + private Boolean enabled = true; + /** + * 所属应用 + */ + @NotNull(message = "所属应用不能为空") + private Long appId; + /** + * 备注 + */ + @Schema(description = "备注") + private String remark; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppPermissionsActionParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppPermissionsActionParam.java new file mode 100644 index 00000000..b60bd32f --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppPermissionsActionParam.java @@ -0,0 +1,59 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.app; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import cn.topiam.employee.common.enums.PermissionActionType; + +import lombok.Data; + +/** + * AppPermissionsActionParam + * + * @author TopIAM + * Created by support@topiam.cn on 2022/9/1 00:18 + */ +@Data +@Valid +public class AppPermissionsActionParam implements Serializable { + + @Serial + private static final long serialVersionUID = -6391182747252245592L; + + /** + * 权限类型 + */ + @NotNull(message = "权限类型") + private PermissionActionType type; + /** + * 权限值 + */ + @NotEmpty(message = "权限值") + private String value; + /** + * 权限描述 + */ + @NotEmpty(message = "权限描述") + private String name; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppSaveConfigParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppSaveConfigParam.java new file mode 100644 index 00000000..c9ef45d5 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/app/AppSaveConfigParam.java @@ -0,0 +1,52 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.app; + +import java.io.Serializable; +import java.util.Map; + +import javax.validation.constraints.NotNull; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 更新应用配置入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/7/18 23:26 + */ +@Data +@Schema(description = "更新应用配置入参") +public class AppSaveConfigParam implements Serializable { + + /** + * id + */ + @Schema(description = "应用id") + @NotNull(message = "ID不能为空") + private Long id; + + /** + * 配置 + */ + @Schema(description = "配置") + @NotNull(message = "配置不能为空") + private Map config; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/authentication/IdentityProviderCreateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/authentication/IdentityProviderCreateParam.java new file mode 100644 index 00000000..f3ff9631 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/authentication/IdentityProviderCreateParam.java @@ -0,0 +1,87 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.authentication; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import com.alibaba.fastjson2.JSONObject; + +import cn.topiam.employee.common.enums.IdentityProviderCategory; +import cn.topiam.employee.common.enums.IdentityProviderType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 源创建参数入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/21 21:21 + */ +@Data +@Schema(description = "认证源保存入参") +public class IdentityProviderCreateParam implements Serializable { + @Serial + private static final long serialVersionUID = -1440230086940289961L; + + /** + * 认证源名称 + */ + @NotBlank(message = "认证源名称不能为空") + @Schema(description = "认证源名称") + private String name; + + /** + * 提供商 + */ + @NotNull(message = "提供商不能为空") + @Schema(description = "提供商") + private IdentityProviderType type; + + /** + * 身份源类型 + */ + @NotNull(message = "身份源类型不能为空") + @Schema(description = "身份源类型") + private IdentityProviderCategory category; + + /** + * 配置 + */ + @NotNull(message = "配置不能为空") + @Schema(description = "配置JSON") + private JSONObject config; + + /** + * 是否展示 + */ + @Schema(description = "是否展示") + private Boolean displayed; + + /** + * 备注 + */ + @Schema(description = "备注") + private String remark; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/authentication/InitializeAdminSaveParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/authentication/InitializeAdminSaveParam.java new file mode 100644 index 00000000..9ffe2550 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/authentication/InitializeAdminSaveParam.java @@ -0,0 +1,68 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.authentication; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 初始化管理员入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/7/18 23:26 + */ +@Data +@Schema(description = "初始化管理员入参") +public class InitializeAdminSaveParam implements Serializable { + + @Serial + private static final long serialVersionUID = -6383799540993308458L; + /** + * 用户名 + */ + @Schema(description = "用户名") + @NotBlank(message = "用户名不能为空") + private String username; + /** + * 密码 + */ + @Schema(description = "密码") + @NotBlank(message = "密码不能为空") + private String password; + /** + * 确认密码 + */ + @Schema(description = "确认密码") + @NotBlank(message = "确认密码不能为空") + private String confirmPassword; + /** + * 邮箱 + */ + @Schema(description = "邮箱") + @Email(message = "邮箱格式不正确") + @NotBlank(message = "邮箱不能为空") + private String email; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/identitysource/IdentitySourceConfigSaveParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/identitysource/IdentitySourceConfigSaveParam.java new file mode 100644 index 00000000..312e31c6 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/identitysource/IdentitySourceConfigSaveParam.java @@ -0,0 +1,76 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.identitysource; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import com.alibaba.fastjson2.JSONObject; + +import cn.topiam.employee.common.entity.identitysource.config.JobConfig; +import cn.topiam.employee.common.entity.identitysource.config.StrategyConfig; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 身份源保存配置入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/25 23:01 + */ +@Data +@Schema(description = "保存身份源配置入参") +public class IdentitySourceConfigSaveParam implements Serializable { + @Serial + private static final long serialVersionUID = -1440230086940289961L; + /** + * ID + */ + @Parameter(description = "ID") + @NotEmpty(message = "ID不能为空") + private String id; + + /** + * 提供商配置 + */ + @NotNull(message = "提供商配置不能为空") + @Parameter(description = "提供商配置") + private JSONObject basicConfig; + + /** + * 策略配置 + */ + @NotNull(message = "策略配置不能为空") + @Parameter(description = "策略配置") + private StrategyConfig strategyConfig; + + /** + * 任务配置 + */ + @Valid + @NotNull(message = "任务配置不能为空") + @Parameter(description = "任务配置") + private JobConfig jobConfig; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/identitysource/IdentitySourceCreateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/identitysource/IdentitySourceCreateParam.java new file mode 100644 index 00000000..6a8acbe1 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/identitysource/IdentitySourceCreateParam.java @@ -0,0 +1,65 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.identitysource; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import cn.topiam.employee.common.enums.identityprovider.IdentitySourceProvider; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 身份源保存入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/1 01:37 + */ +@Data +@Schema(description = "身份源保存入参") +public class IdentitySourceCreateParam implements Serializable { + + @Serial + private static final long serialVersionUID = 4217715726184249167L; + /** + * 名称 + */ + @Parameter(description = "名称") + @NotEmpty(message = "名称不能为空") + private String name; + + /** + * 身份源提供商 + */ + @Parameter(description = "身份源提供商") + @NotNull(message = "身份源提供商不能为空") + private IdentitySourceProvider provider; + + /** + * 备注 + */ + @Parameter(description = "备注") + private String remark; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/identitysource/IdentitySourceCreateResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/identitysource/IdentitySourceCreateResult.java new file mode 100644 index 00000000..d950b92e --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/identitysource/IdentitySourceCreateResult.java @@ -0,0 +1,43 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.identitysource; + +import java.io.Serializable; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 身份源创建返回结果 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/21 19:34 + */ +@Data +@AllArgsConstructor +@Schema(description = "身份源创建返回结果") +public class IdentitySourceCreateResult implements Serializable { + /** + * ID + */ + @Parameter(description = "ID") + private String id; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/AdministratorCreateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/AdministratorCreateParam.java new file mode 100644 index 00000000..c975f6eb --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/AdministratorCreateParam.java @@ -0,0 +1,69 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.setting; + +import java.io.Serializable; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 权限创建参数 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/26 21:46 + */ +@Data +@Schema(description = "创建权限入参") +public class AdministratorCreateParam implements Serializable { + /** + * 用户名 + */ + @Schema(description = "用户名") + private String username; + /** + * 邮箱 + */ + @Schema(description = "邮箱") + private String email; + + /** + * 手机号 + */ + @Schema(description = "手机号") + private String phone; + /** + * 密码 + */ + @Schema(description = "密码") + private String password; + + /** + * 头像URL + */ + @Schema(description = "头像URL") + private String avatar; + + /** + * 备注 + */ + @Schema(description = "备注") + private String remark; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/EmailCustomTemplateSaveParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/EmailCustomTemplateSaveParam.java new file mode 100644 index 00000000..3f222c39 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/EmailCustomTemplateSaveParam.java @@ -0,0 +1,61 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.setting; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotBlank; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 邮件模板配置更新参数 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/17 23:20 + */ +@Data +@Schema(description = "邮件模板配置保存入参") +public class EmailCustomTemplateSaveParam implements Serializable { + @Serial + private static final long serialVersionUID = -4437284094645205715L; + + /** + * 发送人 + */ + @Schema(description = "发件人") + @NotBlank(message = "发件人不能为空") + private String sender; + + /** + * 主题 + */ + @Schema(description = "主题") + @NotBlank(message = "邮件主题不能为空") + private String subject; + + /** + * 内容 + */ + @Schema(description = "内容") + @NotBlank(message = "邮件内容不能为空") + private String content; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/GeoIpProviderSaveParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/GeoIpProviderSaveParam.java new file mode 100644 index 00000000..096ce8c3 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/GeoIpProviderSaveParam.java @@ -0,0 +1,56 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.setting; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotNull; + +import com.alibaba.fastjson2.JSONObject; + +import cn.topiam.employee.common.geo.maxmind.enums.GeoLocationProvider; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 保存地理位置服务商配置入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 21:27 + */ +@Data +@Schema(description = "保存地理位置服务商配置入参") +public class GeoIpProviderSaveParam implements Serializable { + @Serial + private static final long serialVersionUID = -6723117700517052520L; + /** + * 地理位置服务商 + */ + @Schema(description = "地理位置提供商") + @NotNull(message = "地理位置提供商不能为空") + private GeoLocationProvider provider; + + /** + * 配置信息 + */ + @Schema(description = "配置信息") + private JSONObject config; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/MailProviderSaveParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/MailProviderSaveParam.java new file mode 100644 index 00000000..f5b10bc8 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/MailProviderSaveParam.java @@ -0,0 +1,79 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.setting; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotNull; + +import cn.topiam.employee.common.message.enums.MailProvider; +import cn.topiam.employee.common.message.enums.MailSafetyType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 保存邮件服务商配置入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 21:27 + */ +@Data +@Schema(description = "保存邮件服务商配置入参") +public class MailProviderSaveParam implements Serializable { + @Serial + private static final long serialVersionUID = -6723117700517052520L; + /** + * 平台 + */ + @Schema(description = "提供商") + @NotNull(message = "邮件提供商不能为空") + private MailProvider provider; + /** + * smtp地址 + */ + @Schema(description = "smtp地址") + private String smtpUrl; + + /** + * 端口 + */ + @Schema(description = "端口") + private Integer port; + + /** + * 安全验证 + */ + @Schema(description = "安全验证") + private MailSafetyType safetyType; + + /** + * 用户名 + */ + @Schema(description = "用户名") + private String username; + + /** + * 密码 + */ + @Schema(description = "密码") + private String secret; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/PasswordPolicySaveParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/PasswordPolicySaveParam.java new file mode 100644 index 00000000..100d3954 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/PasswordPolicySaveParam.java @@ -0,0 +1,108 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.setting; + +import java.io.Serializable; + +import cn.topiam.employee.core.security.password.enums.PasswordComplexityRule; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 设置密码策略保存参数 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/25 21:16 + */ +@Data +@Schema(description = "密码策略保存参数") +public class PasswordPolicySaveParam implements Serializable { + /** + * 密码最大长度 + */ + @Schema(description = "密码最大长度") + private Integer passwordBiggestLength; + + /** + * 密码最小长度 + */ + @Schema(description = "密码最小长度") + private Integer passwordLeastLength; + + /** + * 复杂度 + */ + @Schema(description = "密码复杂度") + private PasswordComplexityRule passwordComplexity; + + /** + * 弱密码检查 + */ + @Schema(description = "弱密码检查") + private Boolean weakPasswordCheck; + + /** + * 账户信息检查 + */ + @Schema(description = "账户信息检查") + private Boolean includeAccountCheck; + + /** + * 不能多少个以上相同字符 + */ + @Schema(description = "不能多少个以上相同字符") + private Integer notSameChars; + + /** + * 历史密码检查 + */ + @Schema(description = "历史密码检查") + private Boolean historyPasswordCheck; + + /** + * 历史密码检查次数 + */ + @Schema(description = "历史密码检查次数") + private Integer historyPasswordCheckCount; + + /** + * 非法序列检查 + */ + @Schema(description = "非法序列检查") + private Boolean illegalSequenceCheck; + + /** + * 密码有效天数 + */ + @Schema(description = "密码有效天数") + private Integer passwordValidDays; + + /** + * 密码过期前提醒天数 + */ + @Schema(description = "密码有效提醒天数") + private Integer passwordValidWarnBeforeDays; + + /** + * 自定义弱密码 + */ + @Schema(description = "自定义弱密码") + private String customWeakPassword; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/SecurityBasicSaveParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/SecurityBasicSaveParam.java new file mode 100644 index 00000000..77c270c2 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/SecurityBasicSaveParam.java @@ -0,0 +1,77 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.setting; + +import java.io.Serializable; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 安全高级保存参数 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/10/4 19:05 + */ +@Data +@Schema(description = "安全高级保存入参") +public class SecurityBasicSaveParam implements Serializable { + /** + * 会话有效时间 + */ + @Parameter(description = "会话有效时间(秒)") + private Integer sessionValidTime; + + /** + * 短信验证码有效时间 + */ + @Parameter(description = "短信验证码有效时间(秒)") + private Integer smsCodeValidTime; + + /** + * 记住我有效时间 + */ + @Schema(description = "记住我有效时间(秒)") + private Integer rememberMeValidTime; + + /** + * 用户并发数 + */ + @Parameter(description = "用户并发数") + private Integer sessionMaximum; + + /** + * 连续登录失败持续时间 + */ + @Parameter(description = "连续登录失败持续时间") + private Integer loginFailureDuration; + + /** + * 连续登录失败次数 + */ + @Parameter(description = "连续登录失败次数") + private Integer loginFailureCount; + + /** + * 自动解锁时间(分) + */ + @Parameter(description = "自动解锁时间(分)") + private Integer autoUnlockTime; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/SecurityCaptchaSaveParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/SecurityCaptchaSaveParam.java new file mode 100644 index 00000000..63e457cb --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/SecurityCaptchaSaveParam.java @@ -0,0 +1,55 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.setting; + +import java.io.Serializable; + +import javax.validation.constraints.NotNull; + +import com.alibaba.fastjson2.JSONObject; + +import cn.topiam.employee.common.enums.CaptchaProviderType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 行为验证码保存入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/10/4 19:05 + */ +@Data +@Schema(description = "行为验证码保存入参") +public class SecurityCaptchaSaveParam implements Serializable { + /** + * 验证码提供商 + */ + @Parameter(description = "验证码提供商") + @NotNull(message = "验证码提供商不能为空") + private CaptchaProviderType provider; + + /** + * 验证码配置 + */ + @Parameter(description = "验证码配置") + @NotNull(message = "验证码配置不能为空") + private JSONObject config; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/SecurityMfaSaveParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/SecurityMfaSaveParam.java new file mode 100644 index 00000000..1684768e --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/SecurityMfaSaveParam.java @@ -0,0 +1,55 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.setting; + +import java.io.Serializable; +import java.util.List; + +import javax.validation.constraints.NotNull; + +import cn.topiam.employee.common.enums.MfaFactor; +import cn.topiam.employee.common.enums.MfaMode; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 安全MFA配置保存入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/10/4 19:05 + */ +@Data +@Schema(description = "安全MFA配置保存入参") +public class SecurityMfaSaveParam implements Serializable { + /** + * mode + */ + @NotNull(message = "MFA 模式不能为空") + @Parameter(description = "MFA 模式") + private MfaMode mode; + + /** + * factors + */ + @Parameter(description = "MFA 因素") + @NotNull(message = "MFA 因素不能为空") + private List factors; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/SmsProviderSaveParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/SmsProviderSaveParam.java new file mode 100644 index 00000000..e89f1c17 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/SmsProviderSaveParam.java @@ -0,0 +1,75 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.setting; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +import javax.validation.constraints.NotNull; + +import com.alibaba.fastjson2.JSONObject; + +import cn.topiam.employee.common.entity.setting.config.SmsConfig; +import cn.topiam.employee.common.enums.Language; +import cn.topiam.employee.common.message.enums.SmsProvider; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 保存短信服务商创建请求入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/7/31 19:34 + */ +@Data +@Schema(description = "保存短信验证服务入参") +public class SmsProviderSaveParam implements Serializable { + @Serial + private static final long serialVersionUID = 4125843198392920166L; + + /** + * 平台 + */ + @Schema(description = "提供商") + @NotNull(message = "短信提供商不能为空") + private SmsProvider provider; + + /** + * 配置JSON串 + */ + @Schema(description = "配置JSON串") + @NotNull(message = "配置不能为空") + private JSONObject config; + + /** + * 场景语言 + */ + @Schema(description = "场景语言") + @NotNull(message = "场景语言不能为空") + private Language language; + + /** + * 短信模板配置 + */ + @Schema(description = "短信模板配置") + @NotNull(message = "短信模板配置不能为空") + private List templates; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/StorageConfigSaveParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/StorageConfigSaveParam.java new file mode 100644 index 00000000..d56aa982 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/save/setting/StorageConfigSaveParam.java @@ -0,0 +1,56 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.save.setting; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotNull; + +import com.alibaba.fastjson2.JSONObject; + +import cn.topiam.employee.common.storage.enums.StorageProvider; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 保存存储配置入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 21:27 + */ +@Data +@Schema(description = "保存存储配置入参") +public class StorageConfigSaveParam implements Serializable { + @Serial + private static final long serialVersionUID = -6723117700517052520L; + /** + * provider + */ + @NotNull(message = "存储提供商不能为空") + @Schema(description = "存储提供商") + private StorageProvider provider; + /** + * config + */ + @NotNull(message = "存储提供商配置不能为空") + @Schema(description = "配置JSON串") + private JSONObject config; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/setting/SmsProviderConfigResult.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/setting/SmsProviderConfigResult.java new file mode 100644 index 00000000..c795412c --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/setting/SmsProviderConfigResult.java @@ -0,0 +1,79 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.setting; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +import cn.topiam.employee.common.entity.setting.config.SmsConfig; +import cn.topiam.employee.common.message.enums.SmsProvider; +import cn.topiam.employee.common.message.sms.SmsProviderConfig; + +import lombok.Builder; +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 短信服务商配置查询结果 + * + * @author TopIAM + */ +@Data +@Builder +@Schema(description = "短信服务商配置查询结果") +public class SmsProviderConfigResult implements Serializable { + + @Serial + private static final long serialVersionUID = -2667374916357438335L; + /** + * 平台 + */ + @Parameter(description = "提供商") + private SmsProvider provider; + /** + * 配置 + */ + @Parameter(description = "参数配置") + private SmsProviderConfig config; + + /** + * 配置 + */ + @Parameter(description = "模板配置") + private List templates; + /** + * 描述 + */ + @Parameter(description = "描述") + private String desc; + + /** + * 是否启用 + */ + @Parameter(description = "是否启用") + private Boolean enabled; + + /** + * 语言 + */ + @Parameter(description = "语言") + private String language; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/account/OrganizationUpdateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/account/OrganizationUpdateParam.java new file mode 100644 index 00000000..a8da4cb8 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/account/OrganizationUpdateParam.java @@ -0,0 +1,82 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.update.account; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import cn.topiam.employee.common.enums.OrganizationType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 编辑组织架构入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:16 + */ +@Data +@Schema(description = "修改组织机构入参") +public class OrganizationUpdateParam implements Serializable { + @Serial + private static final long serialVersionUID = 4570955457331971748L; + + /** + * id + */ + @Schema(description = "组织ID") + @NotBlank(message = "ID不能为空") + private String id; + + /** + * 名称 + */ + @Schema(description = "组织名称") + @NotBlank(message = "组织名称不能为空") + private String name; + + /** + * 类型 + */ + @Schema(description = "组织类型") + @NotNull(message = "组织类型不能为空") + private OrganizationType type; + + /** + * 描述 + */ + @Schema(description = "组织描述") + private String desc; + + /** + * 排序 + */ + @Schema(description = "组织排序") + private String order; + + /** + * 是否启用 + */ + @Schema(description = "是否启用") + private Boolean enabled; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/account/ResetPasswordParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/account/ResetPasswordParam.java new file mode 100644 index 00000000..08a594e7 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/account/ResetPasswordParam.java @@ -0,0 +1,81 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.update.account; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +import javax.validation.constraints.NotBlank; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +import cn.topiam.employee.common.enums.ListEnumDeserializer; +import cn.topiam.employee.common.enums.MessageNoticeChannel; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 重置密码入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:16 + */ +@Data +@Schema(description = "重置密码入参") +public class ResetPasswordParam implements Serializable { + @Serial + private static final long serialVersionUID = -6616249172773611157L; + /** + * ID + */ + @Schema(description = "用户ID") + @NotBlank(message = "用户ID不能为空") + private String id; + + /** + * 密码 + */ + @Schema(description = "密码") + @NotBlank(message = "密码不能为空") + private String password; + + /** + * 重置密码配置 + */ + @Schema(description = "重置密码配置") + private PasswordResetConfig passwordResetConfig; + + @Data + public static class PasswordResetConfig implements Serializable { + /** + * 启用通知 + */ + @Schema(description = "启用通知") + private Boolean enableNotice; + + /** + * 消息类型 + */ + @Schema(description = "消息类型") + @JsonDeserialize(using = ListEnumDeserializer.class) + private List noticeChannels; + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/account/UserGroupUpdateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/account/UserGroupUpdateParam.java new file mode 100644 index 00000000..b8c3f75e --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/account/UserGroupUpdateParam.java @@ -0,0 +1,65 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.update.account; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotBlank; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 编辑用户入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:16 + */ +@Data +@Schema(description = "修改用户分组入参") +public class UserGroupUpdateParam implements Serializable { + @Serial + private static final long serialVersionUID = -6616249172773611157L; + /** + * ID + */ + @Schema(description = "用户组ID") + @NotBlank(message = "用户组ID不能为空") + private String id; + /** + * 用户名 + */ + @Schema(description = "用户组名称") + @NotBlank(message = "用户组名称不能为空") + private String name; + + /** + * 用户组编码 + */ + @Schema(description = "用户组编码") + @NotBlank(message = "用户组编码不能为空") + private String code; + + /** + * 备注 + */ + @Schema(description = "备注") + private String remark; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/account/UserUpdateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/account/UserUpdateParam.java new file mode 100644 index 00000000..6520f970 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/account/UserUpdateParam.java @@ -0,0 +1,111 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.update.account; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDate; + +import javax.validation.constraints.NotBlank; + +import cn.topiam.employee.common.enums.UserStatus; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 编辑用户入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/11 23:16 + */ +@Data +@Schema(description = "修改用户入参") +public class UserUpdateParam implements Serializable { + @Serial + private static final long serialVersionUID = -6616249172773611157L; + /** + * ID + */ + @Schema(description = "用户ID") + @NotBlank(message = "用户ID不能为空") + private String id; + + /** + * 邮箱 + */ + @Schema(description = "邮箱") + private String email; + + /** + * 手机号 + */ + @Schema(description = "手机号") + private String phone; + + /** + * 头像URL + */ + @Schema(description = "头像URL") + private String avatar; + + /** + * 姓名 + */ + @Schema(description = "姓名") + @NotBlank(message = "姓名不能为空") + private String fullName; + + /** + * 昵称 + */ + @Schema(description = "昵称") + @NotBlank(message = "昵称不能为空") + private String nickName; + + /** + * 身份证号 + */ + @Schema(description = "身份证号") + private String idCard; + + /** + * 地址 + */ + @Schema(description = "地址") + private String address; + + /** + * 备注 + */ + @Schema(description = "备注") + private String remark; + + /** + * 过期日期 + */ + @Schema(description = "过期日期") + private LocalDate expireDate; + + /** + * status + */ + @Schema(description = "状态") + private UserStatus status; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/AppPermissionPolicyUpdateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/AppPermissionPolicyUpdateParam.java new file mode 100644 index 00000000..a8559d31 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/AppPermissionPolicyUpdateParam.java @@ -0,0 +1,89 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.update.app; + +import java.io.Serializable; + +import javax.validation.constraints.NotNull; + +import cn.topiam.employee.common.enums.PolicyEffect; +import cn.topiam.employee.common.enums.PolicyObjectType; +import cn.topiam.employee.common.enums.PolicySubjectType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 修改策略入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/26 21:46 + */ +@Data +@Schema(description = "修改策略入参") +public class AppPermissionPolicyUpdateParam implements Serializable { + /** + * 所属应用 + */ + @NotNull(message = "资源所属应用不能为空") + @Parameter(description = "所属应用") + private Long appId; + + /** + * 授权主体id + */ + @NotNull(message = "主键id不能为空") + @Parameter(description = "主键id") + private Long id; + /** + * 授权主体id + */ + @NotNull(message = "授权主体id不能为空") + @Parameter(description = "授权主体id") + private String subjectId; + + /** + * 权限主体类型(用户、角色、分组、组织机构) + */ + @NotNull(message = "授权主体类型不能为空") + @Parameter(description = "授权主体类型") + private PolicySubjectType subjectType; + + /** + * 权限客体ID + */ + @NotNull(message = "权限客体ID不能为空") + @Parameter(description = "授权客体id") + private Long objectId; + + /** + * 权限客体类型(权限、角色) + */ + @NotNull(message = "权限客体类型不能为空") + @Parameter(description = "授权客体类型") + private PolicyObjectType objectType; + + /** + * 授权作用 + */ + @NotNull(message = "授权作用不能为空") + @Parameter(description = "授权作用") + private PolicyEffect effect; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/AppPermissionResourceUpdateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/AppPermissionResourceUpdateParam.java new file mode 100644 index 00000000..b8392ca7 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/AppPermissionResourceUpdateParam.java @@ -0,0 +1,92 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.update.app; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import cn.topiam.employee.console.pojo.save.app.AppPermissionsActionParam; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; +import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY; + +/** + * 资源修改参数 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/26 21:46 + */ +@Data +@Schema(description = "修改资源入参") +public class AppPermissionResourceUpdateParam implements Serializable { + @Serial + private static final long serialVersionUID = 6021548372386059064L; + /** + * ID + */ + @Schema(accessMode = READ_ONLY) + @NotBlank(message = "ID不能为空") + private String id; + + /** + * 名称 + */ + @Schema(description = "资源名称") + @NotBlank(message = "资源名称不能为空") + private String name; + + /** + * 编码 + */ + @Schema(description = "资源编码") + @NotBlank(message = "资源编码不能为空") + private String code; + + /** + * 描述 + */ + @Schema(description = "资源描述") + @NotBlank(message = "资源描述不能为空") + private String desc; + + /** + * 所属应用 + */ + @Schema(description = "所属应用") + @NotNull(message = "所属应用不能为空") + private Long appId; + + /** + * 是否启用 + */ + @Schema(description = "是否启用") + private Boolean enabled; + + /** + * 资源权限 + */ + @Schema(description = "资源权限") + @NotNull(message = "资源权限不能为空") + private List actions; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/AppSaveConfigParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/AppSaveConfigParam.java new file mode 100644 index 00000000..283c07d4 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/AppSaveConfigParam.java @@ -0,0 +1,59 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.update.app; + +import java.io.Serializable; +import java.util.Map; + +import javax.validation.constraints.NotNull; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 更新应用配置入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/7/18 23:26 + */ +@Data +@Schema(description = "更新应用配置入参") +public class AppSaveConfigParam implements Serializable { + + /** + * id + */ + @Schema(description = "应用id") + @NotNull(message = "ID不能为空") + private String id; + + /** + * 模版 + */ + @Schema(description = "模版") + @NotNull(message = "模版不能为空") + private String template; + + /** + * 配置 + */ + @Schema(description = "配置") + @NotNull(message = "配置不能为空") + private Map config; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/AppUpdateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/AppUpdateParam.java new file mode 100644 index 00000000..cc4f6140 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/AppUpdateParam.java @@ -0,0 +1,62 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.update.app; + +import java.io.Serializable; + +import javax.validation.constraints.NotNull; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 应用修改入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/7/18 23:26 + */ +@Data +@Schema(description = "应用修改入参") +public class AppUpdateParam implements Serializable { + + /** + * id + */ + @Schema(description = "应用id") + @NotNull(message = "ID不能为空") + private Long id; + + /** + * 应用名称 + */ + @Schema(description = "应用名称") + private String name; + + /** + * 应用图标 + */ + @Schema(description = "应用图标") + private String icon; + + /** + * 备注 + */ + @Schema(description = "备注") + private String remark; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/PermissionRoleUpdateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/PermissionRoleUpdateParam.java new file mode 100644 index 00000000..4ab2ad0d --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/app/PermissionRoleUpdateParam.java @@ -0,0 +1,62 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.update.app; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotBlank; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; +import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY; + +/** + * 角色修改参数 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/26 21:46 + */ +@Data +@Schema(description = "修改角色入参") +public class PermissionRoleUpdateParam implements Serializable { + @Serial + private static final long serialVersionUID = 6021548372386059064L; + /** + * ID + */ + @Schema(accessMode = READ_ONLY) + @NotBlank(message = "ID不能为空") + private String id; + /** + * 名称 + */ + @Schema(description = "角色名称") + private String name; + /** + * 编码 + */ + @Schema(description = "角色编码") + private String code; + /** + * 备注 + */ + @Schema(description = "备注") + private String remark; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/authentication/IdpUpdateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/authentication/IdpUpdateParam.java new file mode 100644 index 00000000..2a563d56 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/authentication/IdpUpdateParam.java @@ -0,0 +1,84 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.update.authentication; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import com.alibaba.fastjson2.JSONObject; + +import cn.topiam.employee.common.enums.IdentityProviderType; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 认证源修改参数入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/21 21:21 + */ +@Data +@Schema(description = "认证源修改参数") +public class IdpUpdateParam implements Serializable { + @Serial + private static final long serialVersionUID = -1440230086940289961L; + /** + * ID + */ + @NotBlank(message = "ID不能为空") + @Schema(description = "ID") + private String id; + + /** + * 名称 + */ + @NotBlank(message = "名称不能为空") + @Schema(description = "名称") + private String name; + + /** + * 平台 + */ + @NotNull(message = "平台不能为空") + @Schema(description = "平台") + private IdentityProviderType type; + + /** + * 配置 + */ + @NotNull(message = "配置JSON不能为空") + @Schema(description = "配置JSON") + private JSONObject config; + + /** + * 备注 + */ + @Schema(description = "备注") + private String remark; + + /** + * 是否显示 + */ + @Schema(description = "是否显示") + private Boolean displayed; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/identity/IdentitySourceUpdateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/identity/IdentitySourceUpdateParam.java new file mode 100644 index 00000000..b9c3feb0 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/identity/IdentitySourceUpdateParam.java @@ -0,0 +1,60 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.update.identity; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotEmpty; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 身份源修改参数入参 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/2/25 23:04 + */ +@Data +@Schema(description = "身份源修改参数") +public class IdentitySourceUpdateParam implements Serializable { + @Serial + private static final long serialVersionUID = -1440230086940289961L; + /** + * ID + */ + @Parameter(description = "ID") + @NotEmpty(message = "ID不能为空") + private String id; + + /** + * 名称 + */ + @Parameter(description = "名称") + @NotEmpty(message = "名称不能为空") + private String name; + + /** + * 备注 + */ + @Parameter(description = "备注") + private String remark; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/setting/AdministratorUpdateParam.java b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/setting/AdministratorUpdateParam.java new file mode 100644 index 00000000..b6792e87 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/pojo/update/setting/AdministratorUpdateParam.java @@ -0,0 +1,71 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.pojo.update.setting; + +import java.io.Serial; +import java.io.Serializable; + +import javax.validation.constraints.NotBlank; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; +import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY; + +/** + * 管理员修改参数 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/8/26 21:46 + */ +@Data +@Schema(description = "修改管理员入参") +public class AdministratorUpdateParam implements Serializable { + @Serial + private static final long serialVersionUID = 6021548372386059064L; + /** + * ID + */ + @Schema(accessMode = READ_ONLY) + @NotBlank(message = "ID不能为空") + private String id; + + /** + * 邮箱 + */ + @Schema(description = "邮箱") + private String email; + + /** + * 手机号 + */ + @Schema(description = "手机号") + private String phone; + + /** + * 头像URL + */ + @Schema(description = "头像URL") + private String avatar; + + /** + * 备注 + */ + @Schema(description = "备注") + private String remark; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleAccessDeniedHandler.java b/eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleAccessDeniedHandler.java new file mode 100644 index 00000000..8704c19a --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleAccessDeniedHandler.java @@ -0,0 +1,65 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.security.handler; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; + +import cn.topiam.employee.core.security.util.SecurityUtils; + +import lombok.AllArgsConstructor; + +/** + * 访问拒绝处理程序 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/9/2 22:11 + */ +@AllArgsConstructor +public class ConsoleAccessDeniedHandler implements AccessDeniedHandler { + /** + * 日志 + */ + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * Handles an access denied failure. + * + * @param request that resulted in an AccessDeniedException + * @param response so that the user agent can be advised of the failure + * @param accessDeniedException that caused the invocation + */ + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, + AccessDeniedException accessDeniedException) throws IOException { + logger.info("----------------------------------------------------------"); + logger.info("用户【{}】权限不足", SecurityUtils.getCurrentUser()); + response.sendError(HttpStatus.FORBIDDEN.value(), + accessDeniedException.getLocalizedMessage()); + logger.info("----------------------------------------------------------"); + } + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleAuthenticationEntryPoint.java b/eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleAuthenticationEntryPoint.java new file mode 100644 index 00000000..fc0f0628 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleAuthenticationEntryPoint.java @@ -0,0 +1,72 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.security.handler; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; + +import cn.topiam.employee.support.result.ApiRestResult; +import cn.topiam.employee.support.util.HttpResponseUtils; +import static org.springframework.http.HttpStatus.UNAUTHORIZED; + +/** + * 认证入口点 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/9/2 22:11 + */ +public class ConsoleAuthenticationEntryPoint implements AuthenticationEntryPoint { + /** + * 日志 + */ + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * Commences an authentication scheme. + *

+ * ExceptionTranslationFilter will populate the HttpSession + * attribute named + * AbstractAuthenticationProcessingFilter.SPRING_SECURITY_SAVED_REQUEST_KEY + * with the requested target URL before calling this method. + *

+ * Implementations should modify the headers on the ServletResponse as + * necessary to commence the authentication process. + * + * @param request that resulted in an AuthenticationException + * @param response so that the user agent can begin authentication + * @param authException that caused the invocation + */ + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) { + logger.info("----------------------------------------------------------"); + logger.info("未登录,或登录过期"); + ApiRestResult result = ApiRestResult.builder() + .status(String.valueOf(UNAUTHORIZED.value())).message(StringUtils + .defaultString(authException.getMessage(), UNAUTHORIZED.getReasonPhrase())) + .build(); + HttpResponseUtils.flushResponseJson(response, UNAUTHORIZED.value(), result); + logger.info("----------------------------------------------------------"); + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleAuthenticationFailureHandler.java b/eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleAuthenticationFailureHandler.java new file mode 100644 index 00000000..311beee4 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleAuthenticationFailureHandler.java @@ -0,0 +1,60 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.security.handler; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; + +import cn.topiam.employee.common.enums.SecretType; +import cn.topiam.employee.support.exception.enums.ExceptionStatus; +import cn.topiam.employee.support.result.ApiRestResult; +import cn.topiam.employee.support.util.HttpResponseUtils; +import static cn.topiam.employee.support.constant.EiamConstants.CAPTCHA_CODE_SESSION; + +/** + * 认证失败 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/9/2 22:11 + */ +public class ConsoleAuthenticationFailureHandler implements AuthenticationFailureHandler { + + /** + * Called when an authentication attempt fails. + * + * @param request the request during which the authentication attempt occurred. + * @param response the response. + * @param exception the exception which was thrown to reject the authentication + */ + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, + AuthenticationException exception) { + ApiRestResult result = ApiRestResult.builder() + .status(ExceptionStatus.EX000101.getCode()).message(StringUtils + .defaultString(exception.getMessage(), ExceptionStatus.EX000101.getMessage())) + .build(); + request.getSession().removeAttribute(SecretType.LOGIN.getKey()); + request.getSession().removeAttribute(CAPTCHA_CODE_SESSION); + HttpResponseUtils.flushResponseJson(response, HttpStatus.BAD_REQUEST.value(), result); + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleAuthenticationSuccessHandler.java b/eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleAuthenticationSuccessHandler.java new file mode 100644 index 00000000..e478b1ee --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleAuthenticationSuccessHandler.java @@ -0,0 +1,60 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.security.handler; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; + +import cn.topiam.employee.common.enums.SecretType; +import cn.topiam.employee.support.result.ApiRestResult; +import cn.topiam.employee.support.util.HttpResponseUtils; +import static cn.topiam.employee.support.constant.EiamConstants.CAPTCHA_CODE_SESSION; + +/** + * 认证成功处理程序 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/9/2 22:11 + */ +public class ConsoleAuthenticationSuccessHandler implements AuthenticationSuccessHandler { + + private final Logger logger = LoggerFactory + .getLogger(ConsoleAuthenticationSuccessHandler.class); + + /** + * Called when a user has been successfully authenticated. + * + * @param request the request which caused the successful authentication + * @param response the response + * @param authentication the Authentication object which was created during + */ + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) { + ApiRestResult result = ApiRestResult. builder().result("success").build(); + request.getSession().removeAttribute(SecretType.LOGIN.getKey()); + request.getSession().removeAttribute(CAPTCHA_CODE_SESSION); + HttpResponseUtils.flushResponseJson(response, HttpStatus.OK.value(), result); + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleLogoutSuccessHandler.java b/eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleLogoutSuccessHandler.java new file mode 100644 index 00000000..85c88a50 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/security/handler/ConsoleLogoutSuccessHandler.java @@ -0,0 +1,67 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.security.handler; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; + +import cn.topiam.employee.core.context.ServerContextHelp; +import cn.topiam.employee.support.result.ApiRestResult; +import cn.topiam.employee.support.util.HttpResponseUtils; +import cn.topiam.employee.support.util.HttpUrlUtils; +import static cn.topiam.employee.common.constants.AuthorizeConstants.FE_LOGIN; +import static cn.topiam.employee.support.context.ServletContextHelp.acceptIncludeTextHtml; +import static cn.topiam.employee.support.result.ApiRestResult.SUCCESS; + +/** + * 注销成功 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/9/2 22:11 + */ +public class ConsoleLogoutSuccessHandler implements LogoutSuccessHandler { + /** + * 日志 + */ + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException { + //@formatter:off + boolean isTextHtml = acceptIncludeTextHtml(request); + if (response.isCommitted()) { + return; + } + if (!isTextHtml) { + HttpResponseUtils.flushResponseJson(response, HttpStatus.OK.value(), + ApiRestResult.builder().status(SUCCESS).build()); + return; + } + response.sendRedirect(HttpUrlUtils.format(ServerContextHelp.getConsolePublicBaseUrl() + FE_LOGIN)); + //@formatter:on + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/security/handler/package-info.java b/eiam-console/src/main/java/cn/topiam/employee/console/security/handler/package-info.java new file mode 100644 index 00000000..dfd341b0 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/security/handler/package-info.java @@ -0,0 +1,24 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +/** + * 处理器 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/10/29 23:12 + */ +package cn.topiam.employee.console.security.handler; diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/security/listener/ConsoleAuthenticationFailureEventListener.java b/eiam-console/src/main/java/cn/topiam/employee/console/security/listener/ConsoleAuthenticationFailureEventListener.java new file mode 100644 index 00000000..10ec7056 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/security/listener/ConsoleAuthenticationFailureEventListener.java @@ -0,0 +1,95 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.security.listener; + +import java.util.Optional; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationListener; +import org.springframework.lang.NonNull; +import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent; + +import cn.topiam.employee.audit.entity.Actor; +import cn.topiam.employee.audit.enums.EventStatus; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.audit.event.AuditEventPublish; +import cn.topiam.employee.common.entity.setting.AdministratorEntity; +import cn.topiam.employee.common.enums.UserType; +import cn.topiam.employee.common.repository.setting.AdministratorRepository; +import cn.topiam.employee.core.security.userdetails.UserDetails; +import cn.topiam.employee.support.context.ApplicationContextHelp; +import static cn.topiam.employee.core.security.util.SecurityUtils.getFailureMessage; + +/** + * 认证失败 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/9/3 22:42 + */ +public class ConsoleAuthenticationFailureEventListener implements + ApplicationListener { + private final Logger logger = LoggerFactory + .getLogger(ConsoleAuthenticationFailureEventListener.class); + + /** + * Handle an application event. + * + * @param event the event to respond to + */ + @Override + public void onApplicationEvent(@NonNull AbstractAuthenticationFailureEvent event) { + //@formatter:off + AuditEventPublish publish = ApplicationContextHelp.getBean(AuditEventPublish.class); + String content = getFailureMessage(event); + logger.error("认证失败 [{}]",content); + String principal = (String) event.getAuthentication().getPrincipal(); + if (event.getAuthentication().getPrincipal() instanceof String){ + principal = (String) event.getAuthentication().getPrincipal(); + } + if (event.getAuthentication().getPrincipal() instanceof UserDetails || event.getAuthentication().getPrincipal() instanceof org.springframework.security.core.userdetails.UserDetails){ + principal = ((UserDetails) event.getAuthentication().getPrincipal()).getUsername(); + } + if (StringUtils.isNotBlank(principal)){ + Optional optional = getAdministratorRepository().findByUsername(principal); + if (optional.isEmpty()) { + // 手机号 + optional = getAdministratorRepository().findByPhone(principal); + if (optional.isEmpty()) { + // 邮箱 + optional = getAdministratorRepository().findByEmail(principal); + } + } + if (optional.isEmpty()) { + Actor actor = Actor.builder().type(UserType.ADMIN).build(); + publish.publish(EventType.LOGIN_CONSOLE,"账户不存在:"+principal, actor, EventStatus.FAIL); + return; + } + AdministratorEntity administrator = optional.get(); + Actor actor = Actor.builder().id(administrator.getId().toString()).type(UserType.ADMIN).build(); + publish.publish(EventType.LOGIN_CONSOLE,content+":"+administrator.getUsername(), actor,EventStatus.FAIL); + } + //@formatter:on + } + + private AdministratorRepository getAdministratorRepository() { + return ApplicationContextHelp.getBean(AdministratorRepository.class); + } + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/security/listener/ConsoleAuthenticationSuccessEventListener.java b/eiam-console/src/main/java/cn/topiam/employee/console/security/listener/ConsoleAuthenticationSuccessEventListener.java new file mode 100644 index 00000000..00fb5505 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/security/listener/ConsoleAuthenticationSuccessEventListener.java @@ -0,0 +1,91 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.security.listener; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; + +import org.springframework.context.ApplicationListener; +import org.springframework.lang.NonNull; +import org.springframework.security.authentication.event.AuthenticationSuccessEvent; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import com.google.common.collect.Lists; + +import cn.topiam.employee.audit.entity.Target; +import cn.topiam.employee.audit.enums.EventStatus; +import cn.topiam.employee.audit.enums.TargetType; +import cn.topiam.employee.audit.event.AuditEventPublish; +import cn.topiam.employee.common.geo.GeoLocationService; +import cn.topiam.employee.core.security.userdetails.UserDetails; +import cn.topiam.employee.support.context.ApplicationContextHelp; +import cn.topiam.employee.support.context.ServletContextHelp; +import cn.topiam.employee.support.util.IpUtils; +import cn.topiam.employee.support.web.useragent.UserAgent; +import cn.topiam.employee.support.web.useragent.UserAgentUtils; + +import lombok.AllArgsConstructor; +import static cn.topiam.employee.audit.enums.EventType.LOGIN_CONSOLE; + +/** + * 监听登录成功事件 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/9/3 + */ +@AllArgsConstructor +public class ConsoleAuthenticationSuccessEventListener implements + ApplicationListener { + + /** + * Handle an application event. + * + * @param event the event to respond to + */ + @Override + public void onApplicationEvent(@NonNull AuthenticationSuccessEvent event) { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder + .getRequestAttributes(); + AuditEventPublish auditEventPublish = ApplicationContextHelp + .getBean(AuditEventPublish.class); + Object principal = event.getAuthentication().getPrincipal(); + //@formatter:off + //@formatter:on + if (principal instanceof UserDetails) { + //登录事件 + ((UserDetails) principal).setLoginTime(LocalDateTime.now()); + //区域 + ((UserDetails) principal).setGeoLocation(geoLocationService + .getGeoLocation(IpUtils.getIpAddr(ServletContextHelp.getRequest()))); + //浏览器 + UserAgent agent = UserAgentUtils + .getUserAgent(Objects.requireNonNull(attributes).getRequest()); + ((UserDetails) principal).setUserAgent(agent); + + // 审计事件 + //@formatter:off + List targets= Lists.newArrayList(Target.builder().type(TargetType.CONSOLE).build()); + auditEventPublish.publish(LOGIN_CONSOLE, event.getAuthentication(), EventStatus.SUCCESS,targets); + //@formatter:on + } + } + + private final GeoLocationService geoLocationService; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/security/listener/ConsoleLogoutSuccessEventListener.java b/eiam-console/src/main/java/cn/topiam/employee/console/security/listener/ConsoleLogoutSuccessEventListener.java new file mode 100644 index 00000000..6afc632c --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/security/listener/ConsoleLogoutSuccessEventListener.java @@ -0,0 +1,58 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.security.listener; + +import java.util.List; + +import org.springframework.context.ApplicationListener; +import org.springframework.lang.NonNull; +import org.springframework.security.authentication.event.LogoutSuccessEvent; + +import com.google.common.collect.Lists; + +import cn.topiam.employee.audit.entity.Target; +import cn.topiam.employee.audit.enums.EventStatus; +import cn.topiam.employee.audit.enums.TargetType; +import cn.topiam.employee.audit.event.AuditEventPublish; +import cn.topiam.employee.support.context.ApplicationContextHelp; +import static cn.topiam.employee.audit.enums.EventType.*; + +/** + * 退出成功 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/9/3 22:42 + */ +public class ConsoleLogoutSuccessEventListener implements ApplicationListener { + + /** + * Handle an application event. + * + * @param event the event to respond to + */ + @Override + public void onApplicationEvent(@NonNull LogoutSuccessEvent event) { + AuditEventPublish auditEventPublish = ApplicationContextHelp + .getBean(AuditEventPublish.class); + // 审计事件 + //@formatter:off + List targets= Lists.newArrayList(Target.builder().type(TargetType.CONSOLE).build()); + auditEventPublish.publish(LOGOUT_CONSOLE, event.getAuthentication(), EventStatus.SUCCESS,targets); + //@formatter:on + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/security/listener/ConsoleSessionInformationExpiredStrategy.java b/eiam-console/src/main/java/cn/topiam/employee/console/security/listener/ConsoleSessionInformationExpiredStrategy.java new file mode 100644 index 00000000..8fe53213 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/security/listener/ConsoleSessionInformationExpiredStrategy.java @@ -0,0 +1,48 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.security.listener; + +import javax.servlet.http.HttpServletResponse; + +import org.springframework.http.HttpStatus; +import org.springframework.security.web.session.SessionInformationExpiredEvent; +import org.springframework.security.web.session.SessionInformationExpiredStrategy; + +import com.alibaba.fastjson2.JSONObject; + +import cn.topiam.employee.support.result.ApiRestResult; +import cn.topiam.employee.support.util.HttpResponseUtils; +import static cn.topiam.employee.support.exception.enums.ExceptionStatus.EX000203; + +/** + * session 过期 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/7/25 21:08 + */ +public class ConsoleSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy { + + @Override + public void onExpiredSessionDetected(SessionInformationExpiredEvent event) { + HttpServletResponse response = event.getResponse(); + ApiRestResult result = ApiRestResult. builder().status(EX000203.getCode()) + .message(EX000203.getMessage()).build(); + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + HttpResponseUtils.flushResponse(response, JSONObject.toJSONString(result)); + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/security/package-info.java b/eiam-console/src/main/java/cn/topiam/employee/console/security/package-info.java new file mode 100644 index 00000000..3d77c798 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/security/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.security; \ No newline at end of file diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/account/OrganizationService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/OrganizationService.java new file mode 100644 index 00000000..9b6290ff --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/OrganizationService.java @@ -0,0 +1,149 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.account; + +import java.util.List; + +import cn.topiam.employee.common.entity.account.OrganizationEntity; +import cn.topiam.employee.common.enums.DataOrigin; +import cn.topiam.employee.console.pojo.result.account.OrganizationChildResult; +import cn.topiam.employee.console.pojo.result.account.OrganizationResult; +import cn.topiam.employee.console.pojo.result.account.OrganizationRootResult; +import cn.topiam.employee.console.pojo.result.account.OrganizationTreeResult; +import cn.topiam.employee.console.pojo.save.account.OrganizationCreateParam; +import cn.topiam.employee.console.pojo.update.account.OrganizationUpdateParam; + +/** + *

+ * 组织架构 服务类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-09 + */ +public interface OrganizationService { + + /** + * 创建组织架构 + * + * @param param {@link OrganizationCreateParam} + * @return {@link Boolean} + */ + Boolean createOrg(OrganizationCreateParam param); + + /** + * 修改组织架构 + * + * @param param {@link OrganizationUpdateParam} + * @return {@link Boolean} + */ + Boolean updateOrg(OrganizationUpdateParam param); + + /** + * 启用/禁用 + * + * @param id {@link String} + * @param enabled {@link Boolean} + * @return {@link Boolean} + */ + Boolean updateStatus(String id, boolean enabled); + + /** + * 删除组织架构 + * + * @param id {@link List} + * @return {@link Boolean} + */ + Boolean deleteOrg(String id); + + /** + * 根据ID查询组织架构 + * + * @param id {@link String} + * @return {@link OrganizationResult} + */ + OrganizationResult getOrganization(String id); + + /** + * 移动组织机构 + * + * @param id {@link String} + * @param parentId {@link String} + * @return {@link Boolean} + */ + Boolean moveOrganization(String id, String parentId); + + /** + * 查询根组织 + * + * @return {@link OrganizationRootResult} + */ + OrganizationRootResult getRootOrganization(); + + /** + * 查询子组织 + * + * @param parentId {@link String} + * @return {@link OrganizationChildResult} + */ + List getChildOrganization(String parentId); + + /** + * 查询子组织 + * + * @param parentId {@link String} + * @param dataOrigin {@link DataOrigin} + * @param identitySourceId {@link Long} + * @return {@link OrganizationEntity} + */ + List getChildOrgList(String parentId, DataOrigin dataOrigin, + Long identitySourceId); + + /** + * 过滤组织树 + * + * @param keyWord {@link String} 关键字 + * @return {@link List} + */ + List filterOrganizationTree(String keyWord); + + /** + * 根据ID查询组织架构 + * + * @param id {@link String} + * @return {@link OrganizationEntity} + */ + OrganizationEntity getById(String id); + + /** + * 根据外部ID查询组织架构 + * + * @param id {@link String} + * @param identitySourceId {@link Long} + * @return {@link OrganizationEntity} + */ + OrganizationEntity getOrganizationByExternalId(String id, Long identitySourceId); + + /*** + * 批量删除 + * + * @param ids {@link String} + * @return {@link Boolean} + */ + Boolean batchDeleteOrg(String[] ids); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/account/UserAccountAssociateService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/UserAccountAssociateService.java new file mode 100644 index 00000000..bcc6bc49 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/UserAccountAssociateService.java @@ -0,0 +1,26 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.account; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/29 22:46 + */ +public interface UserAccountAssociateService { + +} \ No newline at end of file diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/account/UserGroupService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/UserGroupService.java new file mode 100644 index 00000000..615eeda7 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/UserGroupService.java @@ -0,0 +1,116 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.account; + +import java.util.List; + +import cn.topiam.employee.common.entity.account.UserGroupEntity; +import cn.topiam.employee.common.entity.account.query.UserGroupMemberListQuery; +import cn.topiam.employee.console.pojo.query.account.UserGroupListQuery; +import cn.topiam.employee.console.pojo.result.account.UserGroupListResult; +import cn.topiam.employee.console.pojo.result.account.UserGroupMemberListResult; +import cn.topiam.employee.console.pojo.save.account.UserGroupCreateParam; +import cn.topiam.employee.console.pojo.update.account.UserGroupUpdateParam; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; + +/** + * 用户组service + * + * @author TopIAM + * Created by support@topiam.cn on 2021/10/11 21:29 + */ +public interface UserGroupService { + /** + * 查询用户分组列表 + * + * @param page {@link PageModel} + * @param query {@link UserGroupListQuery} + * @return {@link UserGroupListResult} + */ + Page getUserGroupList(PageModel page, UserGroupListQuery query); + + /** + * 创建用户组 + * + * @param param {@link UserGroupCreateParam} + * @return {@link Boolean} + */ + Boolean createUserGroup(UserGroupCreateParam param); + + /** + * 更新用户组 + * + * @param param {@link UserGroupUpdateParam} + * @return {@link Boolean} + */ + Boolean updateUserGroup(UserGroupUpdateParam param); + + /** + * 删除用户组 + * + * @param id {@link String} + * @return {@link Boolean} + */ + Boolean deleteUserGroup(String id); + + /** + * 根据ID查询用户分组 + * + * @param id {@link Long} + * @return {@link UserGroupEntity} + */ + UserGroupEntity getUserGroup(Long id); + + /** + * 获取用户组成员 + * + * @param query {@link UserGroupMemberListQuery} + * @param page {@link PageModel} + * @return {@link UserGroupMemberListResult} + */ + Page getUserGroupMemberList(PageModel page, + UserGroupMemberListQuery query); + + /** + * 从用户组移除用户 + * + * @param id {@link String} + * @param userId {@link String} + * @return {@link Boolean} + */ + Boolean removeMember(String id, String userId); + + /** + * 添加用户 + * + * @param userIds {@link String} + * @param groupId {@link String} + * @return {@link Boolean} + */ + Boolean addMember(String groupId, String[] userIds); + + /** + * 批量移除用户 + * + * @param id {@link String} + * @param userIds {@link String} + * @return {@link Boolean} + */ + Boolean batchRemoveMember(String id, List userIds); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/account/UserService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/UserService.java new file mode 100644 index 00000000..1aca9d46 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/UserService.java @@ -0,0 +1,172 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.account; + +import java.io.Serializable; +import java.util.List; + +import cn.topiam.employee.common.entity.account.UserEntity; +import cn.topiam.employee.common.entity.account.query.UserListNotInGroupQuery; +import cn.topiam.employee.common.entity.account.query.UserListQuery; +import cn.topiam.employee.common.enums.CheckValidityType; +import cn.topiam.employee.common.enums.UserStatus; +import cn.topiam.employee.console.pojo.result.account.UserListResult; +import cn.topiam.employee.console.pojo.result.account.UserLoginAuditListResult; +import cn.topiam.employee.console.pojo.result.account.UserResult; +import cn.topiam.employee.console.pojo.save.account.UserCreateParam; +import cn.topiam.employee.console.pojo.update.account.ResetPasswordParam; +import cn.topiam.employee.console.pojo.update.account.UserUpdateParam; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; + +/** + *

+ * 用户表 服务类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-07-31 + */ +public interface UserService { + + /** + * 获取用户(分页) + * + * @param page {@link PageModel} + * @param query {@link UserListQuery} + * @return {@link UserListQuery} + */ + Page getUserList(PageModel page, UserListQuery query); + + /** + * 获取用户列表不在当前组 + * + * @param page {@link PageModel} + * @param query {@link UserListNotInGroupQuery } + * @return {@link } + */ + Page getUserListNotInGroup(PageModel page, UserListNotInGroupQuery query); + + /** + * 重置密码 + * + * @param param {@link ResetPasswordParam} 用户入参 + * @return {@link Boolean} 是否成功 + */ + Boolean resetUserPassword(ResetPasswordParam param); + + /** + * 更改用户状态 + * + * @param id {@link Long} + * @param status {@link UserStatus} + * @return {@link Boolean} + */ + boolean changeUserStatus(Long id, UserStatus status); + + /** + * 创建用户 + * + * @param param {@link UserCreateParam} + * @return {@link Boolean} + */ + Boolean createUser(UserCreateParam param); + + /** + * 通过外部ID获取用户 + * + * @param id {@link String} + * @return {@link UserEntity} + */ + UserEntity getByExternalId(String id); + + /** + * 根据ID查询用户 + * + * @param id {@link String} + * @return {@link Boolean} + */ + UserResult getUser(String id); + + /** + * 更新用户 + * + * @param param {@link UserUpdateParam} + * @return {@link Boolean} + */ + boolean updateUser(UserUpdateParam param); + + /** + * 删除用户 + * + * @param id {@link Serializable} + * @return {@link Boolean} + */ + boolean deleteUser(String id); + + /** + * 用户转岗 + * + * @param userId {@link String} + * @param orgId {@link String} + * @return {@link Boolean} + */ + Boolean userTransfer(String userId, String orgId); + + /** + * 批量删除用户 + * + * @param ids {@link String} + * @return {@link Boolean} + */ + Boolean batchDeleteUser(String[] ids); + + /** + * 参数有效性验证 + * + * @param type {@link CheckValidityType} + * @param value {@link String} + * @param id {@link Long} + * @return {@link Boolean} + */ + Boolean userParamCheck(CheckValidityType type, String value, Long id); + + /** + * 查询组织成员数量 + * + * @param orgId {@link String} + * @return {@link Long} + */ + Long getOrgMemberCount(String orgId); + + /** + * 批量删除 + * + * @param removeIds {@link Long} + */ + void deleteBatchUser(List removeIds); + + /** + * 查看用户登录日志 + * + * @param id {@link Long} + * @param pageModel {@link PageModel} + * @return {@link List} + */ + Page findUserLoginAuditList(Long id, PageModel pageModel); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/account/UserSocialBindService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/UserSocialBindService.java new file mode 100644 index 00000000..1264cc4a --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/UserSocialBindService.java @@ -0,0 +1,28 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.account; + +/** + * 应用用户关联表 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/12/29 20:25 + */ +public interface UserSocialBindService { + +} \ No newline at end of file diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/OrganizationServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/OrganizationServiceImpl.java new file mode 100644 index 00000000..5b303234 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/OrganizationServiceImpl.java @@ -0,0 +1,408 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.account.impl; + +import java.util.*; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import com.google.common.collect.Lists; + +import cn.topiam.employee.audit.context.AuditContext; +import cn.topiam.employee.audit.entity.Target; +import cn.topiam.employee.audit.enums.TargetType; +import cn.topiam.employee.common.entity.account.OrganizationEntity; +import cn.topiam.employee.common.enums.DataOrigin; +import cn.topiam.employee.common.repository.account.OrganizationRepository; +import cn.topiam.employee.console.converter.account.OrganizationConverter; +import cn.topiam.employee.console.pojo.result.account.OrganizationChildResult; +import cn.topiam.employee.console.pojo.result.account.OrganizationResult; +import cn.topiam.employee.console.pojo.result.account.OrganizationRootResult; +import cn.topiam.employee.console.pojo.result.account.OrganizationTreeResult; +import cn.topiam.employee.console.pojo.save.account.OrganizationCreateParam; +import cn.topiam.employee.console.pojo.update.account.OrganizationUpdateParam; +import cn.topiam.employee.console.service.account.OrganizationService; +import cn.topiam.employee.console.service.account.UserService; +import cn.topiam.employee.support.context.ApplicationContextHelp; +import cn.topiam.employee.support.util.BeanUtils; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import static cn.topiam.employee.support.constant.EiamConstants.ROOT_NODE; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_BY; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_TIME; + +/** + *

+ * 组织架构 服务实现类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-09 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class OrganizationServiceImpl implements OrganizationService { + public static final String SEPARATE = "/"; + + /** + * 创建组织架构 + * + * @param param {@link OrganizationCreateParam} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean createOrg(OrganizationCreateParam param) { + //保存 + OrganizationEntity entity = orgDataConverter.orgCreateParamConvertToEntity(param); + //查询父节点 + Optional parent = organizationRepository.findById(param.getParentId()); + //新建 + organizationRepository.save(entity); + //展示路径枚举 + parent.ifPresent(org -> entity + .setDisplayPath(StringUtils.isEmpty(org.getPath()) ? SEPARATE + entity.getName() + : org.getDisplayPath() + SEPARATE + entity.getName())); + //设置路径枚举 + parent.ifPresent( + org -> entity.setPath(StringUtils.isEmpty(org.getPath()) ? SEPARATE + entity.getId() + : org.getPath() + SEPARATE + entity.getId())); + //存在父节点,更改为非叶子节点 + if (parent.isPresent() && parent.get().getLeaf()) { + organizationRepository.updateIsLeaf(parent.get().getId(), false); + } + AuditContext + .setTarget(Target.builder().id(entity.getId()).type(TargetType.ORGANIZATION).build()); + return true; + } + + /** + * 修改组织架构 + * + * @param param {@link OrganizationCreateParam} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean updateOrg(OrganizationUpdateParam param) { + OrganizationEntity organization = orgDataConverter.orgUpdateParamConvertToEntity(param); + Optional optional = this.organizationRepository.findById(param.getId()); + if (optional.isPresent()) { + OrganizationEntity entity = optional.get(); + //如果修改了名字,递归修改和该组织有关所有节点信息的展示路径 + if (!optional.get().getName().equals(param.getName())) { + //修改名称 + organization.setDisplayPath(getNewDisplayPath(param.getId(), param.getName(), + entity.getPath(), entity.getDisplayPath())); + //递归更改下级名称 + if (!entity.getLeaf()) { + recursiveUpdateDisplayPath(entity.getId(), entity.getId(), param.getName()); + } + } + //修改 + BeanUtils.merge(organization, entity, LAST_MODIFIED_BY, LAST_MODIFIED_TIME); + organizationRepository.save(entity); + AuditContext.setTarget( + Target.builder().id(entity.getId()).type(TargetType.ORGANIZATION).build()); + return true; + } + return false; + } + + /** + * 递归修改显示名称 + * + * @param parentId {@link String} 上级ID + * @param id {@link String} ID 要更改名称的节点ID + * @param name {@link String} 名称 + */ + protected void recursiveUpdateDisplayPath(String parentId, String id, String name) { + List childNodes = organizationRepository.findByParentId(parentId); + for (OrganizationEntity org : childNodes) { + org.setDisplayPath(getNewDisplayPath(id, name, org.getPath(), org.getDisplayPath())); + organizationRepository.save(org); + //存在下级节点 + if (!org.getLeaf()) { + //递归处理 + recursiveUpdateDisplayPath(org.getId(), id, name); + } + } + } + + /** + * 获取新的显示路径 + * + * @param id {@link String} 要更改的ID + * @param name {@link String} 要更改的新名称 + * @param path {@link String} 路径 + * @param displayPath {@link String} 显示路径 + * @return {@link String} 新显示名称 + */ + private String getNewDisplayPath(String id, String name, String path, String displayPath) { + // 修改名称有个问题,如果名称一致,使用替换就会出问题,所以使用KEY=VALUE更改 + String[] pathIds = path.split(SEPARATE); + String[] pathNames = displayPath.split(SEPARATE); + //路径数据 + Map pathData = new LinkedHashMap<>(); + if (pathIds.length == pathNames.length) { + //i=1起步,以为切割数组,第0位为"" + for (int i = 1; i < pathNames.length; i++) { + pathData.put(pathIds[i], pathNames[i]); + } + } + pathData.put(id, name); + //封装 displayPath + StringBuilder newDisplayPath = new StringBuilder(); + + for (Map.Entry next : pathData.entrySet()) { + newDisplayPath.append(SEPARATE).append(next.getValue()); + } + return newDisplayPath.toString(); + } + + /** + * 启用/禁用 + * + * @param id {@link String} + * @param enabled {@link Boolean} + * @return {@link Boolean} + */ + @Override + public Boolean updateStatus(String id, boolean enabled) { + return organizationRepository.updateStatus(id, enabled) > 0; + } + + /** + * 删除组织架构 + * + * @param id {@link List} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean deleteOrg(String id) { + Optional optional = organizationRepository.findById(id); + if (optional.isPresent()) { + //是否存在子节点 + List list = organizationRepository.findByParentId(id); + if (CollectionUtils.isEmpty(list)) { + //查询当前机构和当前机构下子机构下是否存在用户,不存在删除,存在抛出异常 + Long count = ApplicationContextHelp.getBean(UserService.class) + .getOrgMemberCount(id); + if (count > 0) { + throw new RuntimeException("删除机构失败,当前机构下存在用户"); + } + //删除 + organizationRepository.deleteById(id); + AuditContext + .setTarget(Target.builder().id(id).type(TargetType.ORGANIZATION).build()); + //查询该子节点上级组织机构是否存在子节点,如果不存在,更改 leaf=true + list = organizationRepository.findByParentId(optional.get().getParentId()); + //不存在子部门,且父节点非根节点,执行更改 leaf=true 操作 + if (CollectionUtils.isEmpty(list) + && !StringUtils.equals(ROOT_NODE, optional.get().getParentId())) { + organizationRepository.updateIsLeaf(optional.get().getParentId(), true); + } + return true; + } + throw new RuntimeException("删除机构失败,当前机构下存在子机构"); + } + return false; + } + + /** + * 组织机构详情 + * + * @param id {@link String} + * @return {@link OrganizationResult} + */ + @Override + public OrganizationResult getOrganization(String id) { + Optional entity = organizationRepository.findById(id); + return entity.map(orgDataConverter::entityConvertToOrgDetailResult).orElse(null); + } + + /** + * 移动组织机构 + * + * @param id {@link String} + * @param parentId {@link String} + * @return {@link Boolean} + */ + @Override + public Boolean moveOrganization(String id, String parentId) { + AuditContext.setTarget(Target.builder().type(TargetType.ORGANIZATION).id(id).build(), + Target.builder().type(TargetType.ORGANIZATION).id(parentId).build()); + Optional organization = organizationRepository.findById(id); + if (organization.isPresent()) { + OrganizationEntity entity = organization.get(); + Optional parent = organizationRepository.findById(parentId); + if (parent.isPresent()) { + if (parent.get().getLeaf()) { + parent.get().setLeaf(false); + organizationRepository.save(parent.get()); + } + entity.setParentId(parentId); + //父级路径 + entity.setPath( + StringUtils.defaultString(parent.get().getPath()) + SEPARATE + entity.getId()); + //父级展示路径 + entity.setDisplayPath(StringUtils.defaultString(parent.get().getDisplayPath()) + + SEPARATE + entity.getName()); + } + // 判断当前节点下是否还存在子节点,不存在更改此节点为叶子节点 + List childList = organizationRepository.findByParentId(id); + if (CollectionUtils.isEmpty(childList)) { + entity.setLeaf(true); + } + organizationRepository.save(entity); + //存在子组织,递归更改子组织 path 和 displayPath + recursiveUpdateChildNodePathAndDisplayPath(entity.getId()); + return true; + } + return false; + } + + /** + * 递归修改子节点 path 和 displayPath + * + * @param id {@link String } 当前节点ID + */ + private void recursiveUpdateChildNodePathAndDisplayPath(String id) { + Optional organization = organizationRepository.findById(id); + if (organization.isPresent()) { + OrganizationEntity entity = organization.get(); + List childList = organizationRepository.findByParentId(id); + for (OrganizationEntity e : childList) { + e.setPath(entity.getPath() + SEPARATE + entity.getId()); + e.setDisplayPath(entity.getDisplayPath() + SEPARATE + entity.getName()); + organizationRepository.save(e); + //存在下级节点 + if (!e.getLeaf()) { + //递归处理 + recursiveUpdateChildNodePathAndDisplayPath(e.getId()); + } + } + } + } + + /** + * 查询根组织 + * + * @return {@link OrganizationResult} + */ + @Override + public OrganizationRootResult getRootOrganization() { + OrganizationEntity entity = organizationRepository.findById(ROOT_NODE).orElse(null); + return orgDataConverter.entityConvertToRootOrgListResult(entity); + } + + /** + * 查询子组织 + * + * @param parentId {@link String} + * @return {@link OrganizationResult} + */ + @Override + public List getChildOrganization(String parentId) { + List entityList = organizationRepository + .findByParentIdOrderByOrderAsc(parentId); + return orgDataConverter.entityConvertToChildOrgListResult(entityList); + } + + /** + * 查询子组织 + * + * @param parentId {@link String} + * @param dataOrigin {@link DataOrigin} + * @param identitySourceId {@link Long} + * @return {@link OrganizationEntity} + */ + @Override + public List getChildOrgList(String parentId, DataOrigin dataOrigin, + Long identitySourceId) { + return organizationRepository.findByParentIdAndDataOriginAndIdentitySourceId(parentId, + dataOrigin, identitySourceId); + } + + /** + * 过滤组织树 + * + * @param keyWord {@link String} 关键字 name | code + * @return {@link List} + */ + @Override + public List filterOrganizationTree(String keyWord) { + List list = organizationRepository.findByNameLikeOrCodeLike(keyWord); + if (!CollectionUtils.isEmpty(list)) { + List parentIds = Lists.newArrayList(); + for (OrganizationEntity entity : list) { + parentIds.addAll(Lists.newArrayList(entity.getPath().split(SEPARATE))); + } + if (!CollectionUtils.isEmpty(parentIds)) { + List entityList = organizationRepository + .findByIdInOrderByOrderAsc(parentIds); + return orgDataConverter.entityConvertToChildOrgTreeListResult(null, entityList); + } + } + return Lists.newArrayList(); + } + + @Override + public OrganizationEntity getById(String id) { + return organizationRepository.findById(id).orElse(null); + } + + /** + * 根据外部ID查询组织架构 + * + * @param id {@link String} + * @param identitySourceId {@link Long} + * @return {@link OrganizationEntity} + */ + @Override + public OrganizationEntity getOrganizationByExternalId(String id, Long identitySourceId) { + return organizationRepository.findByExternalIdAndIdentitySourceId(id, identitySourceId); + } + + /** + * 批量删除组织 + * + * @param ids {@link String} + * @return {@link Boolean} + */ + @Override + public Boolean batchDeleteOrg(String[] ids) { + organizationRepository.deleteAllById(Arrays.asList(ids)); + return true; + } + + /** + * 组织架构数据映射器 + */ + private final OrganizationConverter orgDataConverter; + /** + * OrganizationRepository + */ + private final OrganizationRepository organizationRepository; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/UserAccountAssociateServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/UserAccountAssociateServiceImpl.java new file mode 100644 index 00000000..b7f4c19a --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/UserAccountAssociateServiceImpl.java @@ -0,0 +1,33 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.account.impl; + +import org.springframework.stereotype.Service; + +import cn.topiam.employee.console.service.account.UserAccountAssociateService; + +import lombok.AllArgsConstructor; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/29 22:48 + */ +@Service +@AllArgsConstructor +public class UserAccountAssociateServiceImpl implements UserAccountAssociateService { +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/UserGroupServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/UserGroupServiceImpl.java new file mode 100644 index 00000000..5f1b07fa --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/UserGroupServiceImpl.java @@ -0,0 +1,235 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.account.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.querydsl.QPageRequest; +import org.springframework.stereotype.Service; + +import com.google.common.collect.Lists; +import com.querydsl.core.types.Predicate; + +import cn.topiam.employee.audit.context.AuditContext; +import cn.topiam.employee.audit.entity.Target; +import cn.topiam.employee.audit.enums.TargetType; +import cn.topiam.employee.common.entity.account.UserGroupEntity; +import cn.topiam.employee.common.entity.account.UserGroupMemberEntity; +import cn.topiam.employee.common.entity.account.po.UserPO; +import cn.topiam.employee.common.entity.account.query.UserGroupMemberListQuery; +import cn.topiam.employee.common.repository.account.UserGroupMemberRepository; +import cn.topiam.employee.common.repository.account.UserGroupRepository; +import cn.topiam.employee.console.converter.account.UserGroupConverter; +import cn.topiam.employee.console.pojo.query.account.UserGroupListQuery; +import cn.topiam.employee.console.pojo.result.account.UserGroupListResult; +import cn.topiam.employee.console.pojo.result.account.UserGroupMemberListResult; +import cn.topiam.employee.console.pojo.save.account.UserGroupCreateParam; +import cn.topiam.employee.console.pojo.update.account.UserGroupUpdateParam; +import cn.topiam.employee.console.service.account.UserGroupService; +import cn.topiam.employee.support.exception.TopIamException; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.util.BeanUtils; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_BY; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_TIME; + +/** + * 用户组实现service + * + * @author TopIAM + * Created by support@topiam.cn on 2021/10/11 21:30 + */ +@Slf4j +@Service +@AllArgsConstructor +public class UserGroupServiceImpl implements UserGroupService { + /** + * 查询用户分组列表 + * + * @param page {@link PageModel} + * @param query {@link UserGroupListQuery} + * @return {@link UserGroupListResult} + */ + @Override + public Page getUserGroupList(PageModel page, UserGroupListQuery query) { + //查询条件 + Predicate predicate = userGroupConverter.queryUserGroupListParamConvertToPredicate(query); + //分页条件 + QPageRequest request = QPageRequest.of(page.getCurrent(), page.getPageSize()); + //查询映射 + org.springframework.data.domain.Page list = userGroupRepository + .findAll(predicate, request); + return userGroupConverter.userGroupEntityConvertToUserGroupResult(list); + } + + /** + * 创建用户组 + * + * @param param {@link UserGroupCreateParam} + * @return {@link Boolean} + */ + @Override + public Boolean createUserGroup(UserGroupCreateParam param) { + UserGroupEntity entity = userGroupConverter.userGroupCreateParamConvertToEntity(param); + userGroupRepository.save(entity); + AuditContext.setTarget( + Target.builder().id(entity.getId().toString()).type(TargetType.USER_GROUP).build()); + return true; + } + + /** + * 更新用户组 + * + * @param param {@link UserGroupUpdateParam} + * @return {@link Boolean} + */ + @Override + public Boolean updateUserGroup(UserGroupUpdateParam param) { + UserGroupEntity entity = userGroupConverter.userGroupUpdateParamConvertToEntity(param); + UserGroupEntity details = getUserGroup(Long.valueOf(param.getId())); + BeanUtils.merge(entity, details, LAST_MODIFIED_TIME, LAST_MODIFIED_BY); + userGroupRepository.save(details); + AuditContext.setTarget( + Target.builder().id(details.getId().toString()).type(TargetType.USER_GROUP).build()); + return true; + } + + /** + * 删除用户组 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Override + public Boolean deleteUserGroup(String id) { + Optional optional = userGroupRepository.findById(Long.valueOf(id)); + //用户不存在 + if (optional.isEmpty()) { + AuditContext.setContent("操作失败,用户组不存在"); + log.warn(AuditContext.getContent()); + throw new TopIamException(AuditContext.getContent()); + } + userGroupRepository.deleteById(Long.valueOf(id)); + AuditContext.setTarget(Target.builder().id(id).type(TargetType.USER_GROUP).build()); + return true; + } + + /** + * 获取用户组内分组列表 + * + * @param query {@link UserGroupMemberListQuery} + * @return {@link UserGroupMemberListResult} + */ + @Override + public Page getUserGroupMemberList(PageModel model, + UserGroupMemberListQuery query) { + org.springframework.data.domain.Page page = userGroupMemberRepository + .getUserGroupMemberList(query, PageRequest.of(model.getCurrent(), model.getPageSize())); + return userGroupConverter.userPoConvertToGroupMemberListResult(page); + } + + /** + * 从用户组移除用户 + * + * @param id {@link String} + * @param userId {@link String} + * @return {@link Boolean} + */ + @Override + public Boolean removeMember(String id, String userId) { + //查询关联关系 + userGroupMemberRepository.deleteByGroupIdAndUserId(Long.valueOf(id), Long.valueOf(userId)); + AuditContext.setTarget(Target.builder().id(userId).type(TargetType.USER).build(), + Target.builder().id(id).type(TargetType.USER_GROUP).build()); + return true; + } + + /** + * 添加用户 + * + * @param userIds {@link String} + * @param groupId {@link String} + * @return {@link Boolean} + */ + @Override + public Boolean addMember(String groupId, String[] userIds) { + List list = new ArrayList<>(); + Lists.newArrayList(userIds).forEach(id -> { + UserGroupMemberEntity member = new UserGroupMemberEntity(); + member.setGroupId(Long.valueOf(groupId)); + member.setUserId(Long.valueOf(id)); + list.add(member); + }); + List targets = new ArrayList<>(Arrays.stream(userIds) + .map(i -> Target.builder().id(i).type(TargetType.USER).build()).toList()); + targets.add(Target.builder().id(groupId).type(TargetType.USER_GROUP).build()); + AuditContext.setTarget(targets); + //添加 + userGroupMemberRepository.saveAll(list); + return true; + } + + /** + * 根据ID查询用户分组 + * + * @param id {@link Long} + * @return {@link UserGroupEntity} + */ + @Override + public UserGroupEntity getUserGroup(Long id) { + return userGroupRepository.findById(id).orElse(null); + } + + /** + * 批量移除用户 + * + * @param userIds {@link String} + * @param id {@link String} + * @return {@link Boolean} + */ + @Override + public Boolean batchRemoveMember(String id, List userIds) { + userIds.forEach(userId -> userGroupMemberRepository + .deleteByGroupIdAndUserId(Long.valueOf(id), Long.valueOf(userId))); + AuditContext.setTarget( + Target.builder().id(StringUtils.join(userIds)).type(TargetType.USER).build(), + Target.builder().id(id).type(TargetType.USER_GROUP).build()); + return true; + } + + /** + * 用户组数据映射 + */ + private final UserGroupConverter userGroupConverter; + /** + * UserGroupRepository + */ + private final UserGroupRepository userGroupRepository; + /** + * UserGroupMemberRepository + */ + private final UserGroupMemberRepository userGroupMemberRepository; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/UserServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/UserServiceImpl.java new file mode 100644 index 00000000..83820e3f --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/UserServiceImpl.java @@ -0,0 +1,604 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.account.impl; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; +import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; + +import com.google.i18n.phonenumbers.NumberParseException; +import com.google.i18n.phonenumbers.PhoneNumberUtil; +import com.google.i18n.phonenumbers.Phonenumber; +import com.querydsl.core.types.ExpressionUtils; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import cn.topiam.employee.audit.context.AuditContext; +import cn.topiam.employee.audit.entity.AuditElasticSearchEntity; +import cn.topiam.employee.audit.entity.Target; +import cn.topiam.employee.audit.enums.TargetType; +import cn.topiam.employee.common.entity.account.*; +import cn.topiam.employee.common.entity.account.po.UserPO; +import cn.topiam.employee.common.entity.account.query.UserListNotInGroupQuery; +import cn.topiam.employee.common.entity.account.query.UserListQuery; +import cn.topiam.employee.common.enums.*; +import cn.topiam.employee.common.repository.account.*; +import cn.topiam.employee.console.converter.account.UserConverter; +import cn.topiam.employee.console.pojo.result.account.UserListResult; +import cn.topiam.employee.console.pojo.result.account.UserLoginAuditListResult; +import cn.topiam.employee.console.pojo.result.account.UserResult; +import cn.topiam.employee.console.pojo.save.account.UserCreateParam; +import cn.topiam.employee.console.pojo.update.account.ResetPasswordParam; +import cn.topiam.employee.console.pojo.update.account.UserUpdateParam; +import cn.topiam.employee.console.service.account.UserService; +import cn.topiam.employee.core.configuration.EiamSupportProperties; +import cn.topiam.employee.core.message.MsgVariable; +import cn.topiam.employee.core.message.mail.MailMsgEventPublish; +import cn.topiam.employee.core.message.sms.SmsMsgEventPublish; +import cn.topiam.employee.support.exception.BadParamsException; +import cn.topiam.employee.support.exception.InfoValidityFailException; +import cn.topiam.employee.support.exception.TopIamException; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.util.BeanUtils; +import cn.topiam.employee.support.validation.annotation.ValidationPhone; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import static cn.topiam.employee.audit.enums.TargetType.*; +import static cn.topiam.employee.common.constants.AuditConstants.getAuditIndexPrefix; +import static cn.topiam.employee.core.message.sms.SmsMsgEventPublish.PASSWORD; +import static cn.topiam.employee.core.message.sms.SmsMsgEventPublish.USERNAME; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_BY; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_TIME; +import static cn.topiam.employee.support.util.PhoneUtils.getPhoneNumber; + +/** + *

+ * 用户表 服务实现类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-07-31 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + + /** + * 获取用户(分页) + * + * @param pageModel {@link PageModel} + * @return {@link UserListQuery} + */ + @Override + public Page getUserList(PageModel pageModel, UserListQuery query) { + org.springframework.data.domain.Page list = userRepository.getUserList(query, + PageRequest.of(pageModel.getCurrent(), pageModel.getPageSize())); + return userConverter.userPoConvertToUserListResult(list); + } + + /** + * 获取用户列表不在当前组 + * + * @param model {@link PageModel} + * @param query {@link UserListNotInGroupQuery } + * @return {@link } + */ + @Override + public Page getUserListNotInGroup(PageModel model, + UserListNotInGroupQuery query) { + org.springframework.data.domain.Page list = userRepository.getUserListNotInGroupId( + query, PageRequest.of(model.getCurrent(), model.getPageSize())); + return userConverter.userPoConvertToUserListResult(list); + } + + /** + * 重置密码 + * + * @param param {@link ResetPasswordParam} 用户修改入参 + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean resetUserPassword(ResetPasswordParam param) { + //additionalContent + Optional optional = userRepository.findById(Long.valueOf(param.getId())); + //用户不存在 + if (optional.isEmpty()) { + AuditContext.setContent("操作失败,用户不存在"); + log.warn(AuditContext.getContent()); + throw new TopIamException(AuditContext.getContent()); + } + UserEntity userEntity = optional.get(); + // 重置用户密码 + String password = new String( + Base64.getUrlDecoder().decode(param.getPassword().getBytes(StandardCharsets.UTF_8)), + StandardCharsets.UTF_8); + password = passwordEncoder.encode(password); + userRepository.updateUserPassword(Long.valueOf(param.getId()), password, + LocalDateTime.now()); + //保存历史密码 + UserHistoryPasswordEntity userHistoryPassword = new UserHistoryPasswordEntity(); + userHistoryPassword.setUserId(String.valueOf(param.getId())); + userHistoryPassword.setPassword(password); + userHistoryPassword.setChangeTime(LocalDateTime.now()); + userHistoryPasswordRepository.save(userHistoryPassword); + AuditContext.setTarget(Target.builder().id(param.getId()).type(TargetType.USER).build()); + + ResetPasswordParam.PasswordResetConfig passwordResetConfig = param.getPasswordResetConfig(); + if (Objects.nonNull(passwordResetConfig) && passwordResetConfig.getEnableNotice() + && org.apache.commons.collections4.CollectionUtils + .isNotEmpty(passwordResetConfig.getNoticeChannels())) { + // 重置密码成功通知 + if (passwordResetConfig.getNoticeChannels().contains(MessageNoticeChannel.MAIL)) { + if (StringUtils.isNotEmpty(userEntity.getEmail())) { + log.warn("不存在用户邮箱信息,未发送用户重置密码邮件。"); + } else { + Map parameter = new HashMap<>(16); + parameter.put(PASSWORD, password); + mailMsgEventPublish.publish(MailType.RESET_PASSWORD_CONFIRM, + userEntity.getEmail(), parameter); + } + } + if (passwordResetConfig.getNoticeChannels().contains(MessageNoticeChannel.SMS)) { + LinkedHashMap parameter = new LinkedHashMap<>(); + parameter.put(USERNAME, userEntity.getUsername()); + parameter.put(PASSWORD, password); + smsMsgEventPublish.publish(SmsType.RESET_PASSWORD_SUCCESS, userEntity.getPhone(), + parameter); + } + } + + return true; + } + + /** + * 更改用户状态 + * + * @param id {@link Long} + * @param status {@link UserStatus} + * @return {@link Boolean} + */ + @Override + public boolean changeUserStatus(Long id, UserStatus status) { + Optional optional = userRepository.findById(id); + //用户不存在 + if (optional.isEmpty()) { + AuditContext.setContent("操作失败,用户不存在"); + log.warn(AuditContext.getContent()); + throw new TopIamException(AuditContext.getContent()); + } + AuditContext.setTarget(Target.builder().id(id.toString()).type(TargetType.USER).build()); + return userRepository.updateUserStatus(id, status) > 0; + } + + /** + * 创建用户 + * + * @param param {@link UserCreateParam} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean createUser(UserCreateParam param) { + if (!ObjectUtils.isEmpty(param.getPhone()) && StringUtils.isNotEmpty(param.getPhone())) { + if (!getPhoneNumber(param.getPhone()).matches(ValidationPhone.PHONE_REGEXP)) { + throw new InfoValidityFailException("手机号格式错误"); + } + Boolean validityPhone = userParamCheck(CheckValidityType.PHONE, param.getPhone(), null); + if (!validityPhone) { + throw new InfoValidityFailException("手机号已存在"); + } + } + Boolean validityEmail = userParamCheck(CheckValidityType.EMAIL, param.getEmail(), null); + if (!validityEmail) { + throw new InfoValidityFailException("邮箱已存在"); + } + Boolean validityUsername = userParamCheck(CheckValidityType.USERNAME, param.getUsername(), + null); + if (!validityUsername) { + throw new InfoValidityFailException("用户名已存在"); + } + //用户信息 + UserEntity user = userConverter.userCreateParamConvertToUserEntity(param); + userRepository.save(user); + //用户详情 + UserDetailEntity detail = userConverter.userCreateParamConvertToUserDetailEntity(param); + detail.setUserId(user.getId()); + userDetailsRepository.save(detail); + //用户组织关联关系 + OrganizationMemberEntity member = new OrganizationMemberEntity(param.getOrganizationId(), + user.getId()); + organizationMemberRepository.save(member); + AuditContext.setTarget(Target.builder().type(USER).id(user.getId().toString()).build(), + Target.builder().type(USER_DETAIL).id(detail.getId().toString()).build()); + + // 发送短信和邮件的欢迎信息(密码通知) + UserCreateParam.PasswordInitializeConfig passwordInitializeConfig = param + .getPasswordInitializeConfig(); + if (Objects.nonNull(passwordInitializeConfig) && passwordInitializeConfig.getEnableNotice() + && org.apache.commons.collections4.CollectionUtils + .isNotEmpty(passwordInitializeConfig.getNoticeChannels())) { + List channels = passwordInitializeConfig.getNoticeChannels(); + if (channels.contains(MessageNoticeChannel.MAIL)) { + Map parameter = new HashMap<>(16); + parameter.put(MsgVariable.CLIENT_DESCRIPTION, + "您的 TopIAM 账户的初始密码是【" + param.getPassword() + "】。"); + mailMsgEventPublish.publish(MailType.WELCOME_MAIL, user.getEmail(), parameter); + } + if (channels.contains(MessageNoticeChannel.SMS)) { + LinkedHashMap parameter = new LinkedHashMap<>(); + parameter.put(USERNAME, user.getUsername()); + parameter.put(PASSWORD, user.getPassword()); + smsMsgEventPublish.publish(SmsType.WELCOME_SMS, user.getPhone(), parameter); + } + } + return true; + } + + /** + * 通过外部ID获取用户 + * + * @param id {@link String} + * @return {@link UserEntity} + */ + @Override + public UserEntity getByExternalId(String id) { + return userRepository.findByExternalId(id).orElse(null); + } + + /** + * 根据ID查询用户 + * + * @param id {@link String} + * @return {@link UserResult} + */ + @Override + public UserResult getUser(String id) { + //查询 + Optional user = userRepository.findById(Long.valueOf(id)); + Optional detail = userDetailsRepository.findByUserId(Long.valueOf(id)); + //映射 + UserEntity userEntity = user.orElse(null); + UserResult userResult = userConverter.entityConvertToUserResult(userEntity, + detail.orElse(null)); + if (Objects.nonNull(userEntity) && StringUtils.isNotEmpty(userEntity.getPhone())) { + userResult.setPhone(userEntity.getPhoneAreaCode() + userEntity.getPhone()); + } + return userResult; + } + + /** + * 更新用户 + * + * @param param {@link UserUpdateParam} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean updateUser(UserUpdateParam param) { + if (StringUtils.isNotBlank(param.getPhone())) { + String phoneNumber = getPhoneNumber(param.getPhone()); + if (!phoneNumber.matches(ValidationPhone.PHONE_REGEXP)) { + throw new InfoValidityFailException("手机号格式错误"); + } + } + //用户信息 + UserEntity toUserEntity = userConverter.userUpdateParamConvertToUserEntity(param); + UserEntity user = getUser(Long.valueOf(param.getId())); + BeanUtils.merge(toUserEntity, user, LAST_MODIFIED_BY, LAST_MODIFIED_TIME); + //如果更改密码到期时间,修改为启用 + if (user.getStatus().equals(UserStatus.EXPIRED_LOCKED)) { + if (toUserEntity.getExpireDate().isAfter(LocalDate.now())) { + user.setStatus(UserStatus.ENABLE); + } + } + userRepository.save(user); + //用户详情 + UserDetailEntity detail = userDetailsRepository.findByUserId(Long.valueOf(param.getId())) + .orElse(new UserDetailEntity()); + UserDetailEntity toUserDetailsEntity = userConverter + .userUpdateParamConvertToUserDetailsEntity(param); + toUserDetailsEntity.setId(detail.getId()); + BeanUtils.merge(toUserDetailsEntity, detail, LAST_MODIFIED_BY, LAST_MODIFIED_TIME); + userDetailsRepository.save(detail); + AuditContext.setTarget(Target.builder().type(USER).id(user.getId().toString()).build(), + Target.builder().type(USER_DETAIL).id(detail.getId().toString()).build()); + return true; + } + + /** + * 删除用户 + * + * @param id {@link Serializable} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean deleteUser(String id) { + Optional optional = userRepository.findById(Long.valueOf(id)); + //管理员不存在 + if (optional.isEmpty()) { + AuditContext.setContent("删除失败,用户不存在"); + log.warn(AuditContext.getContent()); + throw new TopIamException(AuditContext.getContent()); + } + //删除 + userRepository.deleteById(Long.valueOf(id)); + //删除用户详情 + userDetailsRepository.deleteByUserId(Long.valueOf(id)); + //删除组织用户关联关系 + organizationMemberRepository.deleteByUserId(Long.valueOf(id)); + //删除用户组用户详情 + userGroupMemberRepository.deleteByUserId(Long.valueOf(id)); + AuditContext.setTarget(Target.builder().id(id).type(TargetType.USER).build()); + return true; + } + + /** + * 用户转岗 + * + * @param userId {@link String} + * @param orgId {@link String} + * @return {@link Boolean} + */ + @Override + public Boolean userTransfer(String userId, String orgId) { + Optional entity = organizationRepository.findById(orgId); + //additionalContent + if (entity.isEmpty()) { + AuditContext.setContent("操作失败,组织不存在"); + log.warn(AuditContext.getContent()); + throw new TopIamException(AuditContext.getContent()); + } + organizationMemberRepository.deleteByOrgIdAndUserId(orgId, Long.valueOf(userId)); + userRepository.save(null); + AuditContext.setTarget(Target.builder().id(userId).type(TargetType.USER).build()); + return true; + } + + /** + * 批量删除用户 + * + * @param ids {@link String} + * @return {@link Boolean} + */ + @Override + public Boolean batchDeleteUser(String[] ids) { + //删除用户 + userRepository + .deleteAllById(Arrays.stream(ids).map(s -> Long.parseLong(s.trim())).toList()); + //删除用户详情 + userDetailsRepository + .deleteAllByUserId(Arrays.stream(ids).map(s -> Long.parseLong(s.trim())).toList()); + //删除组织用户关系 + organizationMemberRepository + .deleteAllByUserId(Arrays.stream(ids).map(s -> Long.parseLong(s.trim())).toList()); + //删除用户组关系 + userGroupMemberRepository + .deleteAllByUserId(Arrays.stream(ids).map(s -> Long.parseLong(s.trim())).toList()); + return true; + } + + /** + * 参数有效性验证 + * + * @param type {@link CheckValidityType} + * @param value {@link String} + * @param id {@link Long} + * @return {@link Boolean} + */ + @Override + public Boolean userParamCheck(CheckValidityType type, String value, Long id) { + if (StringUtils.isEmpty(value)) { + return true; + } + QUserEntity user = QUserEntity.userEntity; + UserEntity entity = new UserEntity(); + boolean result = false; + // ID存在说明是修改操作,查询一下当前数据 + if (Objects.nonNull(id)) { + entity = userRepository.findById(id).orElse(new UserEntity()); + } + //手机号 + if (CheckValidityType.PHONE.equals(type)) { + try { + //手机号未修改 + if (StringUtils.equals(value, entity.getPhoneAreaCode() + entity.getPhone())) { + return true; + } + Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(value, + "CN"); + BooleanExpression eq = user.phone + .eq(String.valueOf(phoneNumber.getNationalNumber())) + .and(user.phoneAreaCode.eq(String.valueOf(phoneNumber.getCountryCode()))); + result = !userRepository.exists(eq); + } catch (NumberParseException e) { + log.error("校验手机号发生异常", e); + throw new TopIamException("校验手机号发生异常"); + } + } + //邮箱 + if (CheckValidityType.EMAIL.equals(type)) { + if (StringUtils.equals(entity.getEmail(), value)) { + return true; + } + BooleanExpression eq = user.email.eq(value); + result = !userRepository.exists(eq); + } + //用户名 + if (CheckValidityType.USERNAME.equals(type)) { + if (StringUtils.equals(entity.getUsername(), value)) { + return true; + } + BooleanExpression eq = user.username.eq(value); + result = !userRepository.exists(eq); + } + return result; + } + + /** + * 查询组织成员数量 + * + * @param orgId {@link String} + * @return {@link Long} + */ + @Override + public Long getOrgMemberCount(String orgId) { + //条件 + QUserEntity user = QUserEntity.userEntity; + QOrganizationEntity qOrganization = QOrganizationEntity.organizationEntity; + Predicate predicate = user.isNotNull(); + //FIND_IN_SET函数 + BooleanExpression template = Expressions.booleanTemplate( + "FIND_IN_SET({0}, replace({1}, '/', ','))> 0", orgId, qOrganization.path); + predicate = StringUtils.isBlank(orgId) ? predicate + : ExpressionUtils.and(predicate, qOrganization.id.eq(orgId).or(template)); + //构造查询 + JPAQuery jpaQuery = jpaQueryFactory.selectFrom(user).select(user.count()) + .innerJoin(QOrganizationMemberEntity.organizationMemberEntity) + .on(user.id.eq(QOrganizationMemberEntity.organizationMemberEntity.userId)) + .innerJoin(qOrganization) + .on(qOrganization.id.eq(QOrganizationMemberEntity.organizationMemberEntity.orgId)) + .where(predicate); + return jpaQuery.fetch().get(0); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteBatchUser(List removeIds) { + // 删除 + if (!CollectionUtils.isEmpty(removeIds)) { + userRepository.deleteAllById(removeIds); + } + } + + /** + * 查看用户登录日志 + * + * @param id {@link Long} + * @param pageModel {@link PageModel} + * @return {@link List} + */ + @Override + public Page findUserLoginAuditList(Long id, PageModel pageModel) { + //查询入参转查询条件 + NativeSearchQuery nsq = userConverter.auditListRequestConvertToNativeSearchQuery(id, + pageModel); + //查询列表 + SearchHits search = elasticsearchRestTemplate.search(nsq, + AuditElasticSearchEntity.class, IndexCoordinates + .of(getAuditIndexPrefix(eiamSupportProperties.getDemo().isOpen()) + "*")); + //结果转返回结果 + return userConverter.searchHitsConvertToAuditListResult(search, pageModel); + } + + /** + * 获取用户信息 + * + * @param id {@link String} + * @return {@link UserEntity} + */ + private UserEntity getUser(Long id) { + return userRepository.findById(id).orElseThrow(() -> new BadParamsException("用户不存在")); + } + + /** + * 用户数据映射器 + */ + private final UserConverter userConverter; + + /** + * UserRepository + */ + private final UserRepository userRepository; + + /** + * password encoder + */ + private final PasswordEncoder passwordEncoder; + + /** + * 组织 + */ + private final OrganizationRepository organizationRepository; + + /** + * 组织成员 + */ + private final OrganizationMemberRepository organizationMemberRepository; + + /** + * 部门成员 + */ + private final UserGroupMemberRepository userGroupMemberRepository; + + /** + * 用户详情Repository + */ + private final UserDetailRepository userDetailsRepository; + + /** + * JPAQueryFactory + */ + private final JPAQueryFactory jpaQueryFactory; + + /** + * 修改密码历史Repository + */ + private final UserHistoryPasswordRepository userHistoryPasswordRepository; + + /** + * ElasticsearchRestTemplate + */ + private final ElasticsearchRestTemplate elasticsearchRestTemplate; + + /** + * 邮件消息发布 + */ + private final MailMsgEventPublish mailMsgEventPublish; + + /** + * 短信消息发送 + */ + private final SmsMsgEventPublish smsMsgEventPublish; + + /** + * EiamSupportProperties + */ + private final EiamSupportProperties eiamSupportProperties; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/UserSocialBindServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/UserSocialBindServiceImpl.java new file mode 100644 index 00000000..7a5fa7f4 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/impl/UserSocialBindServiceImpl.java @@ -0,0 +1,34 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.account.impl; + +import org.springframework.stereotype.Service; + +import cn.topiam.employee.console.service.account.UserSocialBindService; + +import lombok.AllArgsConstructor; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/12/29 20:25 + */ +@Service +@AllArgsConstructor +public class UserSocialBindServiceImpl implements UserSocialBindService { + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/account/userdetail/UserDetailsServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/userdetail/UserDetailsServiceImpl.java new file mode 100644 index 00000000..b4210fc1 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/account/userdetail/UserDetailsServiceImpl.java @@ -0,0 +1,117 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.account.userdetail; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.AccountExpiredException; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Component; +import org.springframework.util.ObjectUtils; + +import cn.topiam.employee.common.entity.setting.AdministratorEntity; +import cn.topiam.employee.common.enums.UserStatus; +import cn.topiam.employee.common.enums.UserType; +import cn.topiam.employee.common.repository.setting.AdministratorRepository; +import cn.topiam.employee.core.security.authorization.Roles; +import cn.topiam.employee.core.security.userdetails.UserDetails; +import cn.topiam.employee.core.security.userdetails.UserDetailsService; + +/** + * FortressUserDetailsService + * + * @author TopIAM + * Created by support@topiam.cn on 2020/10/25 20:41 + */ +@Component(value = "userDetailsService") +public class UserDetailsServiceImpl implements UserDetailsService { + + private final Logger logger = LoggerFactory.getLogger(UserDetailsServiceImpl.class); + + /** + * Locates the user based on the username. In the actual implementation, the search + * may possibly be case sensitive, or case insensitive depending on how the + * implementation instance is configured. In this case, the UserDetails + * object that comes back may have a username that is of a different case than what + * was actually requested.. + * + * @param username the username identifying the user whose data is required. + * @return a fully populated user record (never null) + * @throws UsernameNotFoundException if the user could not be found or the user has no + * GrantedAuthority + */ + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + // 状态相关 + boolean enabled = true, accountNonLocked = true; + // 权限 + Collection authorities = new ArrayList<>(); + // 用户名 + Optional optional = administratorRepository.findByUsername(username); + if (optional.isEmpty()) { + // 手机号 + optional = administratorRepository.findByPhone(username); + if (optional.isEmpty()) { + // 邮箱 + optional = administratorRepository.findByEmail(username); + } + } + //不存在该用户 + if (optional.isEmpty()) { + logger.info("根据用户名、手机号、邮箱未查询该管理员【{}】", username); + throw new UsernameNotFoundException("用户名或密码错误"); + } + AdministratorEntity administrator = optional.get(); + if (!ObjectUtils.isEmpty(administrator.getStatus())) { + //锁定 + if (administrator.getStatus().equals(UserStatus.LOCKED) + || administrator.getStatus().equals(UserStatus.PASS_WORD_EXPIRED_LOCKED) + || administrator.getStatus().equals(UserStatus.EXPIRED_LOCKED)) { + logger.info("管理员【{}】被锁定", administrator.getUsername()); + accountNonLocked = false; + } + //禁用 + if (administrator.getStatus().equals(UserStatus.DISABLE)) { + logger.info("管理员【{}】被禁用", administrator.getUsername()); + enabled = false; + } + //根据用户类型封装权限 + SimpleGrantedAuthority authority = new SimpleGrantedAuthority(Roles.ADMIN); + authorities.add(authority); + return new UserDetails(String.valueOf(administrator.getId()), + administrator.getUsername(), administrator.getPassword(), UserType.ADMIN, enabled, + true, true, accountNonLocked, authorities); + } + logger.info("管理员【{}】状态异常", administrator.getUsername()); + throw new AccountExpiredException("管理员状态异常"); + } + + /** + * AdministratorRepository + */ + private final AdministratorRepository administratorRepository; + + public UserDetailsServiceImpl(AdministratorRepository administratorRepository) { + this.administratorRepository = administratorRepository; + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/analysis/AnalysisService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/analysis/AnalysisService.java new file mode 100644 index 00000000..560ce92e --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/analysis/AnalysisService.java @@ -0,0 +1,57 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.analysis; + +import java.util.List; + +import cn.topiam.employee.console.pojo.query.analysis.AnalysisQuery; +import cn.topiam.employee.console.pojo.result.analysis.AppVisitRankResult; +import cn.topiam.employee.console.pojo.result.analysis.AuthnQuantityResult; +import cn.topiam.employee.console.pojo.result.analysis.OverviewResult; + +/** + * 统计 service + * + * @author TopIAM + * Created by support@topiam.cn on 2022/11/22 22:25 + */ +public interface AnalysisService { + + /** + * 概述 + * + * @return {@link OverviewResult} + */ + OverviewResult overview(); + + /** + * 认证统计 + * + * @param params {@link AnalysisQuery} + * @return {@link List} + */ + List authnQuantity(AnalysisQuery params); + + /** + * 认证统计 + * + * @param params {@link AnalysisQuery} + * @return {@link List< AppVisitRankResult >} + */ + List appVisitRank(AnalysisQuery params); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/analysis/impl/AnalysisServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/analysis/impl/AnalysisServiceImpl.java new file mode 100644 index 00000000..7bd0cd20 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/analysis/impl/AnalysisServiceImpl.java @@ -0,0 +1,250 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.analysis.impl; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.index.query.RangeQueryBuilder; +import org.elasticsearch.search.aggregations.Aggregation; +import org.elasticsearch.search.aggregations.AggregationBuilders; +import org.elasticsearch.search.aggregations.Aggregations; +import org.elasticsearch.search.aggregations.BucketOrder; +import org.elasticsearch.search.aggregations.bucket.histogram.*; +import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms; +import org.elasticsearch.search.aggregations.bucket.terms.Terms; +import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder; +import org.jetbrains.annotations.NotNull; +import org.springframework.data.elasticsearch.core.ElasticsearchAggregations; +import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; +import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import cn.topiam.employee.audit.entity.AuditElasticSearchEntity; +import cn.topiam.employee.audit.enums.EventStatus; +import cn.topiam.employee.audit.enums.EventType; +import cn.topiam.employee.common.entity.app.AppEntity; +import cn.topiam.employee.common.repository.account.UserRepository; +import cn.topiam.employee.common.repository.app.AppRepository; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; +import cn.topiam.employee.console.pojo.query.analysis.AnalysisQuery; +import cn.topiam.employee.console.pojo.result.analysis.AppVisitRankResult; +import cn.topiam.employee.console.pojo.result.analysis.AuthnQuantityResult; +import cn.topiam.employee.console.pojo.result.analysis.OverviewResult; +import cn.topiam.employee.console.service.analysis.AnalysisService; +import cn.topiam.employee.core.configuration.EiamSupportProperties; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import static cn.topiam.employee.audit.entity.Event.*; +import static cn.topiam.employee.audit.entity.Target.TARGET_ID_KEYWORD; +import static cn.topiam.employee.common.constants.AuditConstants.getAuditIndexPrefix; +import static cn.topiam.employee.support.constant.EiamConstants.DEFAULT_DATE_TIME_FORMATTER_PATTERN; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/11/22 22:25 + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class AnalysisServiceImpl implements AnalysisService { + + // void testLog(Query query) { + // try { + // Method searchRequest = ReflectionUtils.findMethod(Class.forName("org.springframework.data.elasticsearch.core.RequestFactory"), "searchRequest", Query.class, Class.class, IndexCoordinates.class); + // searchRequest.setAccessible(true); + // Object o = ReflectionUtils.invokeMethod(searchRequest, elasticsearchRestTemplate.getRequestFactory(), query, AuditElasticSearchEntity.class, IndexCoordinates.of(AUDIT_INDEX_PREFIX + "*")); + // + // Field source = ReflectionUtils.findField(Class.forName("org.elasticsearch.action.search.SearchRequest"), "source"); + // source.setAccessible(true); + // Object s = ReflectionUtils.getField(source, o); + // log.error("dsl:{}", s); + // } + // catch (Exception e) { + // e.printStackTrace(); + // } + // } + /** + * 概述 + * + * @return {@link OverviewResult} + */ + @Override + public OverviewResult overview() { + OverviewResult result = new OverviewResult(); + result.setAppCount(appRepository.count()); + result.setUserCount(userRepository.count()); + result.setIdpCount(identityProviderRepository.count()); + // 查询今日认证量条件 + RangeQueryBuilder builder = QueryBuilders.rangeQuery(EVENT_TIME) + .gte(LocalDateTime.of(LocalDate.now(), LocalTime.MIN)) + .lte(LocalDateTime.of(LocalDate.now(), LocalTime.MAX)); + BoolQueryBuilder queryBuilder = getBoolQueryBuilder(builder, EventType.LOGIN_PORTAL); + result.setTodayAuthnCount(elasticsearchRestTemplate.count( + new NativeSearchQueryBuilder().withQuery(queryBuilder).build(), + AuditElasticSearchEntity.class, IndexCoordinates + .of(getAuditIndexPrefix(eiamSupportProperties.getDemo().isOpen()) + "*"))); + return result; + } + + /** + * 认证统计 + * + * @param params {@link AnalysisQuery} + * @return {@link List} + */ + @Override + public List authnQuantity(AnalysisQuery params) { + LocalDateTime min = params.getStartTime(); + LocalDateTime max = params.getEndTime(); + AnalysisQuery.Interval timeInterval = params.getTimeInterval(); + // 根据事件月份分组统计认证数量 + DateHistogramAggregationBuilder authCount = AggregationBuilders.dateHistogram("count") + .calendarInterval(timeInterval.getType()) + .extendedBounds( + new LongBounds(min.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(), + max.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli())) + .minDocCount(0).field(EVENT_TIME).timeZone(ZoneId.of(ZONE_ID)) + .format(timeInterval.getFormat()); + // 事件状态group + TermsAggregationBuilder groupBuilder = AggregationBuilders.terms("statusGroup") + .field(EVENT_STATUS).subAggregation(authCount).minDocCount(0); + // 查询条件 + RangeQueryBuilder builder = QueryBuilders.rangeQuery(EVENT_TIME).timeZone(ZONE_ID) + .format(DEFAULT_DATE_TIME_FORMATTER_PATTERN) + .gt(min.format(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMATTER_PATTERN))) + .lt(max.format(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMATTER_PATTERN))); + BoolQueryBuilder queryBuilder = getBoolQueryBuilder(builder, EventType.LOGIN_PORTAL); + NativeSearchQuery authCountBuild = new NativeSearchQueryBuilder().withQuery(queryBuilder) + .withAggregations(groupBuilder).build(); + // testLog(authCountBuild); + SearchHits authCountResult = elasticsearchRestTemplate + .search(authCountBuild, AuditElasticSearchEntity.class, IndexCoordinates + .of(getAuditIndexPrefix(eiamSupportProperties.getDemo().isOpen()) + "*")); + ParsedStringTerms histogram = (ParsedStringTerms) getAggregation(authCountResult, + "statusGroup"); + List authCountList = new ArrayList<>(); + for (Terms.Bucket bucket : histogram.getBuckets()) { + String statusKey = String.valueOf(bucket.getKey()); + Aggregations aggregations = bucket.getAggregations(); + ParsedDateHistogram count = (ParsedDateHistogram) aggregations.asMap().get("count"); + for (Histogram.Bucket countBucket : count.getBuckets()) { + String countKey = countBucket.getKeyAsString(); + authCountList.add(new AuthnQuantityResult(countKey, countBucket.getDocCount(), + Objects.requireNonNull(EventStatus.getType(statusKey)).getDesc())); + } + } + return authCountList; + } + + /** + * 应用热点统计 + * + * @param params {@link AnalysisQuery} + * @return {@link AppVisitRankResult} + */ + @Override + public List appVisitRank(AnalysisQuery params) { + String min = params.getStartTime() + .format(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMATTER_PATTERN)); + String max = params.getEndTime() + .format(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMATTER_PATTERN)); + // 查询条件 + RangeQueryBuilder builder = QueryBuilders.rangeQuery(EVENT_TIME).timeZone(ZONE_ID) + .format(DEFAULT_DATE_TIME_FORMATTER_PATTERN).gt(min).lt(max); + BoolQueryBuilder queryBuilder = getBoolQueryBuilder(builder, EventType.APP_SSO); + // 应用访问频次前10条 + TermsAggregationBuilder groupAppVisit = AggregationBuilders.terms("count") + .field(TARGET_ID_KEYWORD).order(BucketOrder.count(false)).size(10); + NativeSearchQuery appVisitBuild = new NativeSearchQueryBuilder().withQuery(queryBuilder) + .withAggregations(groupAppVisit).build(); + // testLog(appVisitBuild); + SearchHits appVisitResult = elasticsearchRestTemplate + .search(appVisitBuild, AuditElasticSearchEntity.class, IndexCoordinates + .of(getAuditIndexPrefix(eiamSupportProperties.getDemo().isOpen()) + "*")); + ParsedStringTerms appVisitStringTerms = (ParsedStringTerms) getAggregation(appVisitResult, + "count"); + List applicationVisitList = new ArrayList<>(); + for (Terms.Bucket bucket : appVisitStringTerms.getBuckets()) { + String key = String.valueOf(bucket.getKey()); + //单点登录 + String name = getAppName(key); + applicationVisitList.add(new AppVisitRankResult(name, bucket.getDocCount())); + } + return applicationVisitList; + } + + /** + * 获取应用名称 + * + * @param targetId {@link String} + * @return {@link String} + */ + private String getAppName(String targetId) { + if (!StringUtils.hasText(targetId)) { + return null; + } + AppEntity app = appRepository.findById(Long.valueOf(targetId)).orElse(new AppEntity()); + return app.getName(); + } + + private Aggregation getAggregation(SearchHits searchHits, + String groupName) { + ElasticsearchAggregations elasticsearchAggregations = (ElasticsearchAggregations) searchHits + .getAggregations(); + Assert.notNull(elasticsearchAggregations, "聚合查询失败, aggregations为空"); + Aggregations aggregations = elasticsearchAggregations.aggregations(); + return aggregations.asMap().get(groupName); + } + + @NotNull + private BoolQueryBuilder getBoolQueryBuilder(RangeQueryBuilder builder, EventType eventType) { + // 查询今日认证量条件 + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + // 事件类型 + queryBuilder.must(QueryBuilders.termsQuery(EVENT_TYPE, eventType.getCode())); + // 日期条件 + queryBuilder.filter(builder); + return queryBuilder; + } + + private final String ZONE_ID = ZoneId.systemDefault().getId(); + private final EiamSupportProperties eiamSupportProperties; + + private final ElasticsearchRestTemplate elasticsearchRestTemplate; + + private final AppRepository appRepository; + + private final IdentityProviderRepository identityProviderRepository; + + private final UserRepository userRepository; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/analysis/package-info.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/analysis/package-info.java new file mode 100644 index 00000000..dab5f676 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/analysis/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.analysis; \ No newline at end of file diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppAccessPolicyService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppAccessPolicyService.java new file mode 100644 index 00000000..4aeb805e --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppAccessPolicyService.java @@ -0,0 +1,59 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app; + +import cn.topiam.employee.common.entity.app.query.AppAccessPolicyQuery; +import cn.topiam.employee.console.pojo.result.app.AppAccessPolicyResult; +import cn.topiam.employee.console.pojo.save.app.AppAccessPolicyCreateParam; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; + +/** + * 应用访问权限策略 Service + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/4 19:55 + */ +public interface AppAccessPolicyService { + + /** + * 查询应用授权策略列表 + * + * @param pageModel {@link PageModel} + * @param query {@link AppAccessPolicyQuery} + * @return {@link Page} + */ + Page getAppAccessPolicyList(PageModel pageModel, + AppAccessPolicyQuery query); + + /** + * 创建应用授权策略 + * + * @param param {@link AppAccessPolicyCreateParam} + * @return @{link Boolean} + */ + Boolean createAppAccessPolicy(AppAccessPolicyCreateParam param); + + /** + * 删除应用授权策略 + * + * @param id {@link String} + * @return {@link Boolean} + */ + Boolean deleteAppAccessPolicy(String id); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppAccountService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppAccountService.java new file mode 100644 index 00000000..08173052 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppAccountService.java @@ -0,0 +1,58 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app; + +import cn.topiam.employee.common.entity.app.query.AppAccountQuery; +import cn.topiam.employee.console.pojo.result.app.AppAccountListResult; +import cn.topiam.employee.console.pojo.save.app.AppAccountCreateParam; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; + +/** + * 应用账户 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/4 19:07 + */ +public interface AppAccountService { + + /** + * 查询应用账户 + * + * @param pageModel {@link PageModel} + * @param query {@link AppAccountQuery} + * @return {@link Page} + */ + Page getAppAccountList(PageModel pageModel, AppAccountQuery query); + + /** + * 新增应用账户 + * + * @param param {@link AppAccountCreateParam} + * @return {@link Boolean} + */ + Boolean createAppAccount(AppAccountCreateParam param); + + /** + * 删除应用账户 + * + * @param id {@link String} + * @return {@link Boolean} + */ + Boolean deleteAppAccount(String id); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppCertService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppCertService.java new file mode 100644 index 00000000..55674ba9 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppCertService.java @@ -0,0 +1,39 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app; + +import java.util.List; + +import cn.topiam.employee.console.pojo.query.app.AppCertQuery; +import cn.topiam.employee.console.pojo.result.app.AppCertListResult; + +/** + * APP 证书 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/31 20:45 + */ +public interface AppCertService { + /** + * 获取应用证书列表 + * + * @param query {@link AppCertQuery} + * @return {@link List} + */ + List getAppCertListResult(AppCertQuery query); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppPermissionActionService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppPermissionActionService.java new file mode 100644 index 00000000..93de5ce9 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppPermissionActionService.java @@ -0,0 +1,43 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app; + +import java.util.List; + +import cn.topiam.employee.console.pojo.query.app.AppPermissionActionListQuery; +import cn.topiam.employee.console.pojo.result.app.AppPermissionActionListResult; + +/** + *

+ * 权限 服务类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-10 + */ +public interface AppPermissionActionService { + + /** + * 获取资源权限列表 + * + * @param query {@link AppPermissionActionListQuery} + * @return {@link AppPermissionActionListResult} + */ + List getPermissionActionList(AppPermissionActionListQuery query); + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppPermissionPolicyService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppPermissionPolicyService.java new file mode 100644 index 00000000..5fdbc213 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppPermissionPolicyService.java @@ -0,0 +1,78 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app; + +import cn.topiam.employee.common.entity.app.query.AppPolicyQuery; +import cn.topiam.employee.console.pojo.result.app.AppPermissionPolicyGetResult; +import cn.topiam.employee.console.pojo.result.app.AppPermissionPolicyListResult; +import cn.topiam.employee.console.pojo.save.app.AppPermissionPolicyCreateParam; +import cn.topiam.employee.console.pojo.update.app.AppPermissionPolicyUpdateParam; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; + +/** + *

+ * 权限策略 服务类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-10 + */ +public interface AppPermissionPolicyService { + /** + * 获取资源列表 + * + * @param page {@link PageModel} + * @param query {@link AppPolicyQuery} + * @return {@link AppPermissionPolicyListResult} + */ + Page getPermissionPolicyList(PageModel page, + AppPolicyQuery query); + + /** + * 获取资源 + * + * @param id {@link String} + * @return {@link AppPermissionPolicyGetResult} + */ + AppPermissionPolicyGetResult getPermissionPolicy(String id); + + /** + * 删除资源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + Boolean deletePermissionPolicy(String id); + + /** + * 创建资源 + * + * @param param {@link AppPermissionPolicyCreateParam} + * @return {@link Boolean} + */ + Boolean createPermissionPolicy(AppPermissionPolicyCreateParam param); + + /** + * 更新资源 + * + * @param param {@link AppPermissionPolicyUpdateParam} + * @return {@link Boolean} + */ + Boolean updatePermissionPolicy(AppPermissionPolicyUpdateParam param); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppPermissionResourceService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppPermissionResourceService.java new file mode 100644 index 00000000..36c5e089 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppPermissionResourceService.java @@ -0,0 +1,99 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app; + +import cn.topiam.employee.common.enums.CheckValidityType; +import cn.topiam.employee.console.pojo.query.app.AppResourceListQuery; +import cn.topiam.employee.console.pojo.result.app.AppPermissionResourceGetResult; +import cn.topiam.employee.console.pojo.result.app.AppPermissionResourceListResult; +import cn.topiam.employee.console.pojo.save.app.AppPermissionResourceCreateParam; +import cn.topiam.employee.console.pojo.update.app.AppPermissionResourceUpdateParam; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; + +/** + *

+ * 资源权限 服务类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-10 + */ +public interface AppPermissionResourceService { + /** + * 获取资源列表 + * + * @param page {@link PageModel} + * @param query {@link AppResourceListQuery} + * @return {@link AppPermissionResourceListResult} + */ + Page getPermissionResourceList(PageModel page, + AppResourceListQuery query); + + /** + * 获取资源 + * + * @param id {@link String} + * @return {@link AppPermissionResourceGetResult} + */ + AppPermissionResourceGetResult getPermissionResource(String id); + + /** + * 删除资源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + Boolean deletePermissionResource(String id); + + /** + * 启用/禁用 + * + * @param id {@link Long} + * @param enabled {@link Boolean} + * @return {@link Boolean} + */ + Boolean updateStatus(Long id, boolean enabled); + + /** + * 创建资源 + * + * @param param {@link AppPermissionResourceCreateParam} + * @return {@link Boolean} + */ + Boolean createPermissionResource(AppPermissionResourceCreateParam param); + + /** + * 更新资源 + * + * @param param {@link AppPermissionResourceUpdateParam} + * @return {@link Boolean} + */ + Boolean updatePermissionResource(AppPermissionResourceUpdateParam param); + + /** + * 参数有效性验证 + * + * @param type {@link CheckValidityType} + * @param value {@link String} + * @param appId {@link Long} + * @param id {@link Long} + * @return {@link Boolean} + */ + Boolean permissionResourceParamCheck(CheckValidityType type, String value, Long appId, Long id); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppPermissionRoleService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppPermissionRoleService.java new file mode 100644 index 00000000..31881753 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppPermissionRoleService.java @@ -0,0 +1,100 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app; + +import cn.topiam.employee.common.enums.CheckValidityType; +import cn.topiam.employee.console.pojo.query.app.AppPermissionRoleListQuery; +import cn.topiam.employee.console.pojo.result.app.AppPermissionRoleListResult; +import cn.topiam.employee.console.pojo.result.app.AppPermissionRoleResult; +import cn.topiam.employee.console.pojo.save.app.AppPermissionRoleCreateParam; +import cn.topiam.employee.console.pojo.update.app.PermissionRoleUpdateParam; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; + +/** + *

+ * 角色表 服务类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-10 + */ +public interface AppPermissionRoleService { + + /** + * 获取所有角色(分页) + * + * @param page {@link PageModel} + * @param query {@link AppPermissionRoleListQuery} + * @return {@link AppPermissionRoleListResult} + */ + Page getPermissionRoleList(PageModel page, + AppPermissionRoleListQuery query); + + /** + * 创建角色 + * + * @param param {@link AppPermissionRoleCreateParam} + * @return {@link Boolean} + */ + boolean createPermissionRole(AppPermissionRoleCreateParam param); + + /** + * 更新角色 + * + * @param param {@link PermissionRoleUpdateParam} + * @return {@link Boolean} + */ + boolean updatePermissionRole(PermissionRoleUpdateParam param); + + /** + * 删除角色 + * + * @param ids {@link String} + * @return {@link Boolean} + */ + boolean deletePermissionRole(String ids); + + /** + * 角色详情 + * + * @param id {@link Long} + * @return {@link AppPermissionRoleResult} + */ + AppPermissionRoleResult getPermissionRole(Long id); + + /** + * 参数有效性验证 + * + * @param type {@link CheckValidityType} + * @param value {@link String} + * @param appId {@link Long} + * @param id {@link Long} + * @return {@link Boolean} + */ + Boolean permissionRoleParamCheck(CheckValidityType type, String value, Long appId, Long id); + + /** + * 更新角色状态 + * + * @param id {@link String} + * @param status {@link Boolean} + * @return {@link Boolean} + */ + Boolean updatePermissionRoleStatus(String id, Boolean status); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppSaml2Service.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppSaml2Service.java new file mode 100644 index 00000000..8cb683da --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppSaml2Service.java @@ -0,0 +1,55 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app; + +import java.io.IOException; +import java.io.InputStream; + +import cn.topiam.employee.console.pojo.result.app.ParseSaml2MetadataResult; + +/** + * 应用 Saml2 详情 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/31 20:55 + */ +public interface AppSaml2Service { + /** + * 解析saml2 元数据 + * + * @param inputStream {@link InputStream} + * @return {@link ParseSaml2MetadataResult} + */ + ParseSaml2MetadataResult parseSaml2Metadata(InputStream inputStream); + + /** + * 解析saml2 元数据 + * + * @param metadataUrl {@link String} + * @return {@link ParseSaml2MetadataResult} + */ + ParseSaml2MetadataResult parseSaml2MetadataUrl(String metadataUrl); + + /** + * 下载元数据 + * + * @param appId {@link String} + * @throws IOException; + */ + void downloadSaml2IdpMetadataFile(String appId) throws IOException; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppService.java new file mode 100644 index 00000000..9f8a97f3 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppService.java @@ -0,0 +1,114 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app; + +import java.util.Map; + +import cn.topiam.employee.console.pojo.query.app.AppQuery; +import cn.topiam.employee.console.pojo.result.app.AppCreateResult; +import cn.topiam.employee.console.pojo.result.app.AppGetResult; +import cn.topiam.employee.console.pojo.result.app.AppListResult; +import cn.topiam.employee.console.pojo.save.app.AppCreateParam; +import cn.topiam.employee.console.pojo.update.app.AppSaveConfigParam; +import cn.topiam.employee.console.pojo.update.app.AppUpdateParam; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; + +/** + *

+ * 应用管理 服务类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-07-31 + */ +public interface AppService { + + /** + * 获取应用(分页) + * + * @param pageModel {@link PageModel} + * @param query {@link AppQuery} + * @return {@link AppListResult} + */ + Page getAppList(PageModel pageModel, AppQuery query); + + /** + * 创建应用 + * + * @param param {@link AppCreateParam} + * @return {@link AppCreateResult} + */ + AppCreateResult createApp(AppCreateParam param); + + /** + * 修改应用 + * + * @param param {@link AppUpdateParam} + * @return {@link Boolean} + */ + boolean updateApp(AppUpdateParam param); + + /** + * 删除应用 + * + * @param id {@link Long} + * @return {@link Boolean} + */ + boolean deleteApp(Long id); + + /** + * 获取单个应用详情 + * + * @param id {@link Long} + * @return {@link AppGetResult} + */ + AppGetResult getApp(Long id); + + /** + * 启用应用 + * + * @param id {@link String} + * @return {@link Boolean} + */ + Boolean enableApp(String id); + + /** + * 禁用应用 + * + * @param id {@link String} + * @return {@link Boolean} + */ + Boolean disableApp(String id); + + /** + * 更新应用配置 + * + * @param param {@link AppSaveConfigParam} + * @return {@link Boolean} + */ + Boolean saveAppConfig(AppSaveConfigParam param); + + /** + * 获取应用配置 + * + * @param appId {@link String} + * @return {@link Map} + */ + Object getAppConfig(String appId); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppTemplateService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppTemplateService.java new file mode 100644 index 00000000..5a03a11b --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/AppTemplateService.java @@ -0,0 +1,49 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app; + +import java.util.List; +import java.util.Map; + +import cn.topiam.employee.common.enums.app.AppType; +import cn.topiam.employee.console.pojo.result.app.AppTemplateResult; + +/** + * 应用模板服务 + * + * @author TopIAM + * Created by support@topiam.cn on 2020/11/29 22:22 + */ +public interface AppTemplateService { + /** + * List + * + * @param name {@link String} + * @param type {@link AppType} + * @return {@link List} + */ + List getAppTemplateList(AppType type, String name); + + /** + * List + * + * @param code {@link AppTemplateResult} + * @return {@link List} + */ + List getAppTemplateFormSchema(String code); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/UserIdpBindService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/UserIdpBindService.java new file mode 100644 index 00000000..2359af28 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/UserIdpBindService.java @@ -0,0 +1,46 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app; + +import java.util.List; + +import cn.topiam.employee.console.pojo.result.app.UserIdpBindListResult; + +/** + * 用户身份提供商绑定 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/11 21:10 + */ +public interface UserIdpBindService { + /** + * 解绑用户IDP + * + * @param id {@link String} + * @return {@link Boolean} + */ + Boolean unbindUserIdpBind(String id); + + /** + * 查询用户身份提供商绑定 + * + * @param userId {@link String} + * @return {@link List} + */ + List getUserIdpBindList(String userId); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppAccessPolicyServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppAccessPolicyServiceImpl.java new file mode 100644 index 00000000..81ef49a3 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppAccessPolicyServiceImpl.java @@ -0,0 +1,152 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app.impl; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.querydsl.QPageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.audit.context.AuditContext; +import cn.topiam.employee.audit.entity.Target; +import cn.topiam.employee.audit.enums.TargetType; +import cn.topiam.employee.common.entity.app.AppAccessPolicyEntity; +import cn.topiam.employee.common.entity.app.po.AppAccessPolicyPO; +import cn.topiam.employee.common.entity.app.query.AppAccessPolicyQuery; +import cn.topiam.employee.common.repository.app.AppAccessPolicyRepository; +import cn.topiam.employee.console.converter.app.AppAccessPolicyConverter; +import cn.topiam.employee.console.pojo.result.app.AppAccessPolicyResult; +import cn.topiam.employee.console.pojo.save.app.AppAccessPolicyCreateParam; +import cn.topiam.employee.console.service.app.AppAccessPolicyService; +import cn.topiam.employee.support.exception.TopIamException; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.util.BeanUtils; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_BY; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_TIME; + +/** + * 应用访问权限策略 Service + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/4 19:56 + */ +@Service +@Slf4j +@AllArgsConstructor +public class AppAccessPolicyServiceImpl implements AppAccessPolicyService { + + /** + * 查询应用授权策略列表 + * + * @param pageModel {@link PageModel} + * @param query {@link AppAccessPolicyQuery} + * @return {@link Page} + */ + @Override + public Page getAppAccessPolicyList(PageModel pageModel, + AppAccessPolicyQuery query) { + //分页条件 + QPageRequest request = QPageRequest.of(pageModel.getCurrent(), pageModel.getPageSize()); + //查询映射 + org.springframework.data.domain.Page list = appAccessPolicyRepository + .getAppPolicyList(query, request); + return appAccessPolicyConverter.appPolicyEntityListConvertToAppPolicyResult(list); + } + + /** + * 创建应用授权策略 + * + * @param param {@link AppAccessPolicyCreateParam} + * @return @{link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean createAppAccessPolicy(AppAccessPolicyCreateParam param) { + List list = appAccessPolicyConverter + .appPolicyCreateParamConvertToEntity(param); + //判断是否已经存在 + for (AppAccessPolicyEntity policy : list) { + Optional policyEntity = appAccessPolicyRepository + .findByAppIdAndSubjectIdAndSubjectType(policy.getAppId(), policy.getSubjectId(), + policy.getSubjectType()); + if (policyEntity.isEmpty()) { + appAccessPolicyRepository.save(policy); + AuditContext + .setTarget( + Target.builder().id(policy.getSubjectId()) + .type( + TargetType.getType(policy.getSubjectType().getCode().toLowerCase())) + .build(), + Target.builder().id(policy.getAppId().toString()) + .type(TargetType.APPLICATION).build()); + continue; + } + AppAccessPolicyEntity entity = policyEntity.get(); + BeanUtils.merge(policy, entity, LAST_MODIFIED_TIME, LAST_MODIFIED_BY); + appAccessPolicyRepository.save(entity); + AuditContext.setTarget(Target.builder().id(entity.getSubjectId()) + .type(TargetType.getType(entity.getSubjectType().getCode().toLowerCase())).build(), + Target.builder().id(entity.getAppId().toString()).type(TargetType.APPLICATION) + .build()); + } + return true; + } + + /** + * 删除应用授权策略 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean deleteAppAccessPolicy(String id) { + Optional optional = appAccessPolicyRepository + .findById(Long.valueOf(id)); + //管理员不存在 + if (optional.isEmpty()) { + AuditContext.setContent("删除失败,应用授权策略不存在"); + log.warn(AuditContext.getContent()); + throw new TopIamException(AuditContext.getContent()); + } + appAccessPolicyRepository.deleteById(Long.valueOf(id)); + AppAccessPolicyEntity entity = optional.get(); + AuditContext.setTarget( + Target.builder().id(entity.getSubjectId()) + .type(TargetType.getType(entity.getSubjectType().getCode().toLowerCase())).build(), + Target.builder().id(entity.getAppId().toString()).type(TargetType.APPLICATION).build()); + return true; + } + + /** + * AppPolicyConverter + */ + private final AppAccessPolicyConverter appAccessPolicyConverter; + + /** + * AppPolicyRepository + */ + private final AppAccessPolicyRepository appAccessPolicyRepository; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppAccountServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppAccountServiceImpl.java new file mode 100644 index 00000000..c737416c --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppAccountServiceImpl.java @@ -0,0 +1,129 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app.impl; + +import java.util.Optional; + +import org.springframework.data.querydsl.QPageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.audit.context.AuditContext; +import cn.topiam.employee.audit.entity.Target; +import cn.topiam.employee.audit.enums.TargetType; +import cn.topiam.employee.common.entity.app.AppAccountEntity; +import cn.topiam.employee.common.entity.app.po.AppAccountPO; +import cn.topiam.employee.common.entity.app.query.AppAccountQuery; +import cn.topiam.employee.common.exception.app.AppAccountExistException; +import cn.topiam.employee.common.repository.app.AppAccountRepository; +import cn.topiam.employee.console.converter.app.AppAccountConverter; +import cn.topiam.employee.console.pojo.result.app.AppAccountListResult; +import cn.topiam.employee.console.pojo.save.app.AppAccountCreateParam; +import cn.topiam.employee.console.service.app.AppAccountService; +import cn.topiam.employee.support.exception.TopIamException; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 应用账户 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/6/4 19:07 + */ +@Service +@Slf4j +@AllArgsConstructor +public class AppAccountServiceImpl implements AppAccountService { + + /** + * 查询应用账户 + * + * @param pageModel {@link PageModel} + * @param query {@link AppAccountQuery} + * @return {@link Page} + */ + @Override + public Page getAppAccountList(PageModel pageModel, + AppAccountQuery query) { + //分页条件 + QPageRequest request = QPageRequest.of(pageModel.getCurrent(), pageModel.getPageSize()); + //查询映射 + org.springframework.data.domain.Page list = appAccountRepository + .getAppAccountList(query, request); + return appAccountConverter.appAccountEntityConvertToAppAccountResult(list); + } + + /** + * 新增应用账户 + * + * @param param {@link AppAccountCreateParam} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean createAppAccount(AppAccountCreateParam param) { + Optional optional = appAccountRepository + .findByAppIdAndUserId(param.getAppId(), param.getUserId()); + if (optional.isPresent()) { + throw new AppAccountExistException(); + } + AppAccountEntity entity = appAccountConverter.appAccountCreateParamConvertToEntity(param); + appAccountRepository.save(entity); + AuditContext.setTarget( + Target.builder().id(entity.getAccount()).type(TargetType.USER).build(), + Target.builder().id(entity.getAppId().toString()).type(TargetType.APPLICATION).build()); + return true; + } + + /** + * 删除应用账户 + * + * @param id {@link Long} + * @return {@link String} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean deleteAppAccount(String id) { + Optional optional = appAccountRepository.findById(Long.valueOf(id)); + //管理员不存在 + if (optional.isEmpty()) { + AuditContext.setContent("删除失败,应用账户不存在"); + log.warn(AuditContext.getContent()); + throw new TopIamException(AuditContext.getContent()); + } + appAccountRepository.deleteById(Long.valueOf(id)); + AuditContext.setTarget( + Target.builder().id(optional.get().getId().toString()).type(TargetType.USER).build(), + Target.builder().id(optional.get().getAppId().toString()).type(TargetType.APPLICATION) + .build()); + return true; + } + + /** + * AppAccountConverter + */ + private final AppAccountConverter appAccountConverter; + + /** + * AppAccountRepository + */ + private final AppAccountRepository appAccountRepository; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppCertServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppCertServiceImpl.java new file mode 100644 index 00000000..677604e4 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppCertServiceImpl.java @@ -0,0 +1,59 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app.impl; + +import java.util.List; + +import org.springframework.stereotype.Service; + +import cn.topiam.employee.common.entity.app.AppCertEntity; +import cn.topiam.employee.common.repository.app.AppCertRepository; +import cn.topiam.employee.console.converter.app.AppCertConverter; +import cn.topiam.employee.console.pojo.query.app.AppCertQuery; +import cn.topiam.employee.console.pojo.result.app.AppCertListResult; +import cn.topiam.employee.console.service.app.AppCertService; + +import lombok.AllArgsConstructor; + +/** + * 应用证书 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/31 20:46 + */ +@Service +@AllArgsConstructor +public class AppCertServiceImpl implements AppCertService { + + /** + * 获取应用证书列表 + * + * @param query {@link AppCertQuery} + * @return {@link List} + */ + @Override + public List getAppCertListResult(AppCertQuery query) { + List list = (List) appCertRepository + .findAll(appCertConverter.queryAppCertListParamConvertToPredicate(query)); + return appCertConverter.entityConvertToAppCertListResult(list); + } + + private final AppCertRepository appCertRepository; + + private final AppCertConverter appCertConverter; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppPermissionActionServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppPermissionActionServiceImpl.java new file mode 100644 index 00000000..42f6df35 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppPermissionActionServiceImpl.java @@ -0,0 +1,65 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app.impl; + +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.querydsl.core.types.Predicate; + +import cn.topiam.employee.common.entity.app.AppPermissionResourceEntity; +import cn.topiam.employee.common.repository.app.AppPermissionResourceRepository; +import cn.topiam.employee.console.converter.app.AppPermissionActionConverter; +import cn.topiam.employee.console.pojo.query.app.AppPermissionActionListQuery; +import cn.topiam.employee.console.pojo.result.app.AppPermissionActionListResult; +import cn.topiam.employee.console.service.app.AppPermissionActionService; + +import lombok.RequiredArgsConstructor; + +/** + *

+ * 资源权限 服务类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-10 + */ +@Service +@RequiredArgsConstructor +public class AppPermissionActionServiceImpl implements AppPermissionActionService { + + /** + * 获取资源列表 + * + * @param query {@link AppPermissionActionListQuery} + * @return {@link AppPermissionActionListResult} + */ + @Override + public List getPermissionActionList(AppPermissionActionListQuery query) { + Predicate predicate = appPermissionActionConverter + .appPermissionActionListQueryConvertToPredicate(query); + List list = (List) appPermissionResourceRepository + .findAll(predicate); + return appPermissionActionConverter.entityConvertToResourceActionListResult(list); + } + + private final AppPermissionResourceRepository appPermissionResourceRepository; + + private final AppPermissionActionConverter appPermissionActionConverter; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppPermissionPolicyServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppPermissionPolicyServiceImpl.java new file mode 100644 index 00000000..65ac54f1 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppPermissionPolicyServiceImpl.java @@ -0,0 +1,128 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app.impl; + +import org.springframework.data.querydsl.QPageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.common.entity.app.AppPermissionPolicyEntity; +import cn.topiam.employee.common.entity.app.po.AppPermissionPolicyPO; +import cn.topiam.employee.common.entity.app.query.AppPolicyQuery; +import cn.topiam.employee.common.exception.app.AppPolicyNotExistException; +import cn.topiam.employee.common.repository.app.AppPermissionPolicyRepository; +import cn.topiam.employee.console.converter.app.AppPermissionPolicyConverter; +import cn.topiam.employee.console.pojo.result.app.AppPermissionPolicyGetResult; +import cn.topiam.employee.console.pojo.result.app.AppPermissionPolicyListResult; +import cn.topiam.employee.console.pojo.save.app.AppPermissionPolicyCreateParam; +import cn.topiam.employee.console.pojo.update.app.AppPermissionPolicyUpdateParam; +import cn.topiam.employee.console.service.app.AppPermissionPolicyService; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; + +import lombok.RequiredArgsConstructor; + +/** + *

+ * 权限策略 服务实现类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-10 + */ +@Service +@RequiredArgsConstructor +public class AppPermissionPolicyServiceImpl implements AppPermissionPolicyService { + + /** + * 获取策略列表 + * + * @param page {@link PageModel} + * @param query {@link AppPolicyQuery} + * @return {@link AppPermissionPolicyListResult} + */ + @Override + public Page getPermissionPolicyList(PageModel page, + AppPolicyQuery query) { + org.springframework.data.domain.Page data; + QPageRequest request = QPageRequest.of(page.getCurrent(), page.getPageSize()); + data = appPermissionPolicyRepository.findPage(query, request); + return appPermissionPolicyConverter.entityConvertToPolicyListResult(data); + } + + /** + * 获取策略 + * + * @param id {@link String} + * @return {@link AppPermissionPolicyGetResult} + */ + @Override + public AppPermissionPolicyGetResult getPermissionPolicy(String id) { + return null; + } + + /** + * 删除策略 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Override + public Boolean deletePermissionPolicy(String id) { + Long policyId = Long.valueOf(id); + appPermissionPolicyRepository.findById(policyId) + .orElseThrow(AppPolicyNotExistException::new); + appPermissionPolicyRepository.deleteById(policyId); + return true; + } + + /** + * 创建策略 + * + * @param param {@link AppPermissionPolicyCreateParam} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean createPermissionPolicy(AppPermissionPolicyCreateParam param) { + AppPermissionPolicyEntity resource = appPermissionPolicyConverter + .policyCreateParamConvertToEntity(param); + // 新增策略 + appPermissionPolicyRepository.save(resource); + return true; + } + + /** + * 更新策略 + * + * @param param {@link AppPermissionPolicyUpdateParam} + * @return {@link Boolean} + */ + @Override + public Boolean updatePermissionPolicy(AppPermissionPolicyUpdateParam param) { + AppPermissionPolicyEntity resource = appPermissionPolicyConverter + .policyUpdateParamConvertToEntity(param); + // 更新策略 + appPermissionPolicyRepository.save(resource); + return null; + } + + private final AppPermissionPolicyConverter appPermissionPolicyConverter; + + private final AppPermissionPolicyRepository appPermissionPolicyRepository; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppPermissionResourceServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppPermissionResourceServiceImpl.java new file mode 100644 index 00000000..1820e531 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppPermissionResourceServiceImpl.java @@ -0,0 +1,283 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.querydsl.QPageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.BooleanExpression; + +import cn.topiam.employee.audit.context.AuditContext; +import cn.topiam.employee.audit.entity.Target; +import cn.topiam.employee.audit.enums.TargetType; +import cn.topiam.employee.common.entity.app.AppPermissionActionEntity; +import cn.topiam.employee.common.entity.app.AppPermissionResourceEntity; +import cn.topiam.employee.common.entity.app.QAppPermissionResourceEntity; +import cn.topiam.employee.common.enums.CheckValidityType; +import cn.topiam.employee.common.exception.app.AppResourceNotExistException; +import cn.topiam.employee.common.repository.app.AppPermissionActionRepository; +import cn.topiam.employee.common.repository.app.AppPermissionPolicyRepository; +import cn.topiam.employee.common.repository.app.AppPermissionResourceRepository; +import cn.topiam.employee.console.converter.app.AppPermissionResourceConverter; +import cn.topiam.employee.console.pojo.query.app.AppResourceListQuery; +import cn.topiam.employee.console.pojo.result.app.AppPermissionResourceGetResult; +import cn.topiam.employee.console.pojo.result.app.AppPermissionResourceListResult; +import cn.topiam.employee.console.pojo.save.app.AppPermissionResourceCreateParam; +import cn.topiam.employee.console.pojo.save.app.AppPermissionsActionParam; +import cn.topiam.employee.console.pojo.update.app.AppPermissionResourceUpdateParam; +import cn.topiam.employee.console.service.app.AppPermissionResourceService; +import cn.topiam.employee.support.exception.BadParamsException; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.util.BeanUtils; + +import lombok.RequiredArgsConstructor; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_BY; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_TIME; + +/** + *

+ * 资源权限 服务实现类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-10 + */ +@Service +@RequiredArgsConstructor +public class AppPermissionResourceServiceImpl implements AppPermissionResourceService { + + /** + * 获取资源列表 + * + * @param page {@link PageModel} + * @param query {@link AppResourceListQuery} + * @return {@link AppPermissionResourceListResult} + */ + @Override + public Page getPermissionResourceList(PageModel page, + AppResourceListQuery query) { + org.springframework.data.domain.Page data; + Predicate predicate = appPermissionResourceConverter + .resourcePaginationParamConvertToPredicate(query); + QPageRequest request = QPageRequest.of(page.getCurrent(), page.getPageSize()); + data = appResourceRepository.findAll(predicate, request); + return appPermissionResourceConverter.entityConvertToResourceListResult(data); + } + + /** + * 获取资源 + * + * @param id {@link String} + * @return {@link AppPermissionResourceGetResult} + */ + @Override + public AppPermissionResourceGetResult getPermissionResource(String id) { + AppPermissionResourceEntity resource = appResourceRepository.findById(Long.valueOf(id)) + .orElseThrow(AppResourceNotExistException::new); + return appPermissionResourceConverter.entityConvertToResourceGetResult(resource); + } + + /** + * 删除资源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean deletePermissionResource(String id) { + Long resourceId = Long.valueOf(id); + AppPermissionResourceEntity resource = appResourceRepository.findById(resourceId) + .orElseThrow(AppResourceNotExistException::new); + List actionList = appPermissionActionRepository + .findAllByResource(resource); + List objectIdList = new ArrayList<>( + actionList.stream().map(AppPermissionActionEntity::getId).toList()); + objectIdList.add(resourceId); + appPermissionPolicyRepository.deleteAllByObjectIdIn(objectIdList); + appResourceRepository.deleteById(resourceId); + AuditContext + .setTarget(Target.builder().id(id).type(TargetType.APP_PERMISSION_RESOURCE).build()); + return true; + } + + /** + * 启用/禁用 + * + * @param id {@link String} + * @param enabled {@link Boolean} + * @return {@link Boolean} + */ + @Override + public Boolean updateStatus(Long id, boolean enabled) { + AppPermissionResourceEntity resource = appResourceRepository.findById(Long.valueOf(id)) + .orElseThrow(AppResourceNotExistException::new); + AuditContext.setTarget( + Target.builder().id(id.toString()).type(TargetType.APP_PERMISSION_RESOURCE).build()); + return appPermissionPolicyRepository.updateStatus(id, enabled) > 0; + } + + /** + * 创建资源 + * + * @param param {@link AppPermissionResourceCreateParam} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean createPermissionResource(AppPermissionResourceCreateParam param) { + AppPermissionResourceEntity resource = appPermissionResourceConverter + .resourceCreateParamConvertToEntity(param); + buildActions(param.getActions(), resource); + // 新增资源 + appResourceRepository.save(resource); + AuditContext.setTarget(Target.builder().id(resource.getId().toString()) + .type(TargetType.APP_PERMISSION_RESOURCE).build()); + return true; + } + + /** + * 更新资源 + * + * @param param {@link AppPermissionResourceUpdateParam} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean updatePermissionResource(AppPermissionResourceUpdateParam param) { + AppPermissionResourceEntity resource = appPermissionResourceConverter + .resourceUpdateParamConvertToEntity(param); + AppPermissionResourceEntity entity = getAppPermissionResourceEntity( + Long.valueOf(param.getId())); + buildActions(param.getActions(), resource); + BeanUtils.merge(resource, entity, LAST_MODIFIED_BY, LAST_MODIFIED_TIME); + // 查询资源下所有权限 + List actionList = appPermissionActionRepository + .findAllByResource(resource); + // 取出未删除的权限id + Set reservedSet = resource.getActions().stream().map(AppPermissionActionEntity::getId) + .collect(Collectors.toSet()); + // 过滤要删除的权限id + List removeActions = actionList.stream() + .filter(item -> !reservedSet.contains(item.getId())) + .map(AppPermissionActionEntity::getId).toList(); + appPermissionActionRepository.deleteAllById(removeActions); + // 更新资源 + appResourceRepository.save(entity); + AuditContext.setTarget( + Target.builder().id(param.getId()).type(TargetType.APP_PERMISSION_RESOURCE).build()); + return true; + } + + /** + * 获取应用权限资源 + * + * @param id {@link Long} + * @return {@link AppPermissionResourceEntity} + */ + private AppPermissionResourceEntity getAppPermissionResourceEntity(Long id) { + return appResourceRepository.findById(id) + .orElseThrow(() -> new BadParamsException("应用权限资源不存在")); + } + + /** + * 参数有效性验证 + * + * @param type {@link CheckValidityType} + * @param value {@link String} + * @param appId {@link Long} + * @param id {@link Long} + * @return {@link Boolean} + */ + @SuppressWarnings("DuplicatedCode") + @Override + public Boolean permissionResourceParamCheck(CheckValidityType type, String value, Long appId, + Long id) { + QAppPermissionResourceEntity role = QAppPermissionResourceEntity.appPermissionResourceEntity; + AppPermissionResourceEntity entity = new AppPermissionResourceEntity(); + boolean result = false; + // ID存在说明是修改操作,查询一下当前数据 + if (Objects.nonNull(id)) { + entity = appResourceRepository.findById(id) + .orElseThrow(AppResourceNotExistException::new); + } + //资源名称 + if (CheckValidityType.NAME.equals(type)) { + if (StringUtils.equals(entity.getName(), value)) { + return true; + } + BooleanExpression eq = role.name.eq(value); + eq.and(role.appId.eq(appId)); + result = !appResourceRepository.exists(eq); + } + //资源编码 + if (CheckValidityType.CODE.equals(type)) { + if (StringUtils.equals(entity.getCode(), value)) { + return true; + } + BooleanExpression eq = role.code.eq(value); + eq.and(role.appId.eq(appId)); + result = !appResourceRepository.exists(eq); + } + return result; + } + + /** + * 批量处理actions + * + * @param permissions {@link List} + * @param resource {@link AppPermissionResourceEntity>} + */ + private void buildActions(List permissions, + AppPermissionResourceEntity resource) { + // 权限 + List list = new ArrayList<>(); + for (AppPermissionsActionParam p : permissions) { + AppPermissionActionEntity entity = new AppPermissionActionEntity(); + entity.setResource(resource); + entity.setType(p.getType()); + entity.setName(p.getName()); + //API需要单独处理 + entity.setValue(p.getValue()); + list.add(entity); + } + resource.setActions(list); + } + + private final AppPermissionResourceConverter appPermissionResourceConverter; + + private final AppPermissionResourceRepository appResourceRepository; + /** + * PolicyRepository + */ + private final AppPermissionPolicyRepository appPermissionPolicyRepository; + /** + * ActionRepository + */ + private final AppPermissionActionRepository appPermissionActionRepository; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppPermissionRoleServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppPermissionRoleServiceImpl.java new file mode 100644 index 00000000..5a441b2e --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppPermissionRoleServiceImpl.java @@ -0,0 +1,220 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app.impl; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.querydsl.QPageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.BooleanExpression; + +import cn.topiam.employee.audit.context.AuditContext; +import cn.topiam.employee.audit.entity.Target; +import cn.topiam.employee.audit.enums.TargetType; +import cn.topiam.employee.common.entity.app.AppPermissionRoleEntity; +import cn.topiam.employee.common.entity.app.QAppPermissionRoleEntity; +import cn.topiam.employee.common.enums.CheckValidityType; +import cn.topiam.employee.common.exception.app.AppRoleNotExistException; +import cn.topiam.employee.common.repository.app.AppPermissionPolicyRepository; +import cn.topiam.employee.common.repository.app.AppPermissionRoleRepository; +import cn.topiam.employee.console.converter.app.AppPermissionRoleConverter; +import cn.topiam.employee.console.pojo.query.app.AppPermissionRoleListQuery; +import cn.topiam.employee.console.pojo.result.app.AppPermissionRoleListResult; +import cn.topiam.employee.console.pojo.result.app.AppPermissionRoleResult; +import cn.topiam.employee.console.pojo.save.app.AppPermissionRoleCreateParam; +import cn.topiam.employee.console.pojo.update.app.PermissionRoleUpdateParam; +import cn.topiam.employee.console.service.app.AppPermissionRoleService; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.util.BeanUtils; + +import lombok.RequiredArgsConstructor; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_BY; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_TIME; + +/** + *

+ * 角色表 服务实现类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-10 + */ +@Service +@RequiredArgsConstructor +public class AppPermissionRoleServiceImpl implements AppPermissionRoleService { + + /** + * 获取所有角色(分页) + * + * @param page {@link PageModel} + * @return {@link AppPermissionRoleListResult} + */ + @Override + public Page getPermissionRoleList(PageModel page, + AppPermissionRoleListQuery query) { + org.springframework.data.domain.Page data; + Predicate predicate = appPermissionRoleConverter + .rolePaginationParamConvertToPredicate(query); + QPageRequest request = QPageRequest.of(page.getCurrent(), page.getPageSize()); + data = appPermissionRoleRepository.findAll(predicate, request); + return appPermissionRoleConverter.entityConvertToRolePaginationResult(data); + } + + /** + * 创建系统 + * + * @param param {@link AppPermissionRoleCreateParam} + * @return {@link Boolean} + */ + @Override + public boolean createPermissionRole(AppPermissionRoleCreateParam param) { + AppPermissionRoleEntity entity = appPermissionRoleConverter + .roleCreateParamConvertToEntity(param); + appPermissionRoleRepository.save(entity); + AuditContext.setTarget(Target.builder().id(entity.getId().toString()) + .type(TargetType.APP_PERMISSION_ROLE).build()); + return true; + } + + /** + * @param param {@link PermissionRoleUpdateParam} + * @return {@link Boolean} + */ + @Override + public boolean updatePermissionRole(PermissionRoleUpdateParam param) { + AppPermissionRoleEntity source = appPermissionRoleConverter + .roleUpdateParamConvertToEntity(param); + AppPermissionRoleEntity target = appPermissionRoleRepository + .findById(Long.valueOf(param.getId())).orElseThrow(AppRoleNotExistException::new); + BeanUtils.merge(source, target, LAST_MODIFIED_TIME, LAST_MODIFIED_BY); + appPermissionRoleRepository.save(target); + AuditContext.setTarget(Target.builder().id(target.getId().toString()) + .type(TargetType.APP_PERMISSION_ROLE).build()); + return true; + } + + /** + * 删除角色 + * + * @param ids {@link String} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean deletePermissionRole(String ids) { + List idList = Arrays.stream(ids.split(",")).toList(); + List longIds = idList.stream().map(Long::parseLong).toList(); + appPermissionRoleRepository.deleteAllById(longIds); + // 删除对应策略 + appPermissionPolicyRepository.deleteAllBySubjectIdIn(idList); + appPermissionPolicyRepository.deleteAllByObjectIdIn(longIds); + AuditContext + .setTarget(Target.builder().id(ids).type(TargetType.APP_PERMISSION_ROLE).build()); + return true; + } + + /** + * 角色详情 + * + * @param id {@link Long} + * @return {@link AppPermissionRoleResult} + */ + @Override + public AppPermissionRoleResult getPermissionRole(Long id) { + //查询 + Optional entity = appPermissionRoleRepository.findById(id); + //映射 + return appPermissionRoleConverter.entityConvertToRoleDetailResult(entity.orElse(null)); + } + + /** + * 参数有效性验证 + * + * @param type {@link CheckValidityType} + * @param value {@link String} + * @param id {@link Long} + * @param appId {@link Long} + * @return {@link Boolean} + */ + @SuppressWarnings("DuplicatedCode") + @Override + public Boolean permissionRoleParamCheck(CheckValidityType type, String value, Long appId, + Long id) { + QAppPermissionRoleEntity role = QAppPermissionRoleEntity.appPermissionRoleEntity; + AppPermissionRoleEntity entity = new AppPermissionRoleEntity(); + boolean result = false; + // ID存在说明是修改操作,查询一下当前数据 + if (Objects.nonNull(id)) { + entity = appPermissionRoleRepository.findById(id) + .orElseThrow(AppRoleNotExistException::new); + } + //角色编码 + if (CheckValidityType.CODE.equals(type)) { + if (StringUtils.equals(entity.getCode(), value)) { + return true; + } + BooleanExpression eq = role.code.eq(value); + eq.and(role.appId.eq(appId)); + result = !appPermissionRoleRepository.exists(eq); + } + //角色名称 + if (CheckValidityType.NAME.equals(type)) { + if (StringUtils.equals(entity.getName(), value)) { + return true; + } + BooleanExpression eq = role.name.eq(value); + eq.and(role.appId.eq(appId)); + result = !appPermissionRoleRepository.exists(eq); + } + return result; + } + + /** + * 更新角色状态 + * + * @param id {@link String} + * @param status {@link Boolean} + * @return {@link Boolean} + */ + @Override + public Boolean updatePermissionRoleStatus(String id, Boolean status) { + appPermissionRoleRepository.updateStatus(id, status); + return true; + } + + /** + * 用户数据映射器 + */ + private final AppPermissionRoleConverter appPermissionRoleConverter; + /** + * RoleRepository + */ + private final AppPermissionRoleRepository appPermissionRoleRepository; + /** + * PolicyRepository + */ + private final AppPermissionPolicyRepository appPermissionPolicyRepository; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppSaml2ServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppSaml2ServiceImpl.java new file mode 100644 index 00000000..e05ef34c --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppSaml2ServiceImpl.java @@ -0,0 +1,220 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.opensaml.saml.saml2.metadata.*; +import org.opensaml.xmlsec.signature.KeyInfo; +import org.opensaml.xmlsec.signature.X509Data; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import cn.topiam.employee.application.ApplicationService; +import cn.topiam.employee.application.ApplicationServiceLoader; +import cn.topiam.employee.application.Saml2ApplicationService; +import cn.topiam.employee.application.exception.AppNotExistException; +import cn.topiam.employee.application.exception.ParseSaml2MetadataException; +import cn.topiam.employee.common.entity.app.AppEntity; +import cn.topiam.employee.common.repository.app.AppRepository; +import cn.topiam.employee.common.repository.app.AppSaml2ConfigRepository; +import cn.topiam.employee.console.pojo.result.app.ParseSaml2MetadataResult; +import cn.topiam.employee.console.service.app.AppSaml2Service; +import cn.topiam.employee.core.protocol.Saml2ProtocolConfig; +import cn.topiam.employee.protocol.saml2.idp.util.Saml2Utils; +import cn.topiam.employee.support.context.ServletContextHelp; +import cn.topiam.employee.support.util.CertUtils; + +import lombok.AllArgsConstructor; +import static org.opensaml.saml.common.xml.SAMLConstants.SAML20P_NS; +import static org.opensaml.security.credential.UsageType.SIGNING; +import static org.springframework.http.HttpHeaders.CONTENT_DISPOSITION; + +import static cn.topiam.employee.common.util.SamlKeyStoreProvider.getEntityDescriptors; +import static cn.topiam.employee.common.util.SamlUtils.transformSamlObject2String; + +/** + * 应用SAML详情 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/5/31 20:55 + */ +@SuppressWarnings("DuplicatedCode") +@Service +@AllArgsConstructor +public class AppSaml2ServiceImpl implements AppSaml2Service { + private final Logger logger = LoggerFactory.getLogger(AppSaml2ServiceImpl.class); + + /** + * 解析saml2 元数据 + * + * @param inputStream {@link InputStream} + * @return {@link ParseSaml2MetadataResult} + */ + @Override + public ParseSaml2MetadataResult parseSaml2Metadata(InputStream inputStream) { + List entityDescriptors = getEntityDescriptors(inputStream); + for (EntityDescriptor entityDescriptor : entityDescriptors) { + return getParseSaml2MetadataResult(entityDescriptor); + } + return null; + } + + /** + * 解析saml2 元数据 + * + * @param metadataUrl {@link String} + * @return {@link ParseSaml2MetadataResult} + */ + @Override + public ParseSaml2MetadataResult parseSaml2MetadataUrl(String metadataUrl) { + try { + CloseableHttpClient httpClient = HttpClients.createDefault(); + HttpGet post = new HttpGet(metadataUrl); + CloseableHttpResponse response = httpClient.execute(post); + return parseSaml2Metadata(response.getEntity().getContent()); + } catch (Exception e) { + throw new ParseSaml2MetadataException(); + } + } + + @Override + public void downloadSaml2IdpMetadataFile(String appId) throws IOException { + Optional optional = appRepository.findById(Long.valueOf(appId)); + if (optional.isEmpty()) { + logger.error("下载应用 Metadata 发生异常, 应用 [{}] 不存在!", appId); + throw new AppNotExistException(); + } + ApplicationService applicationService = applicationServiceLoader + .getApplicationService(optional.get().getTemplate()); + Saml2ProtocolConfig config = ((Saml2ApplicationService) applicationService) + .getProtocolConfig(appId); + // Generate MetadataXml + EntityDescriptor entityDescriptor = Saml2Utils.getEntityDescriptor(config); + String metadataXml = transformSamlObject2String(entityDescriptor); + // Response + HttpServletResponse response = ServletContextHelp.getResponse(); + response.setContentType("application/samlmetadata+xml"); + response.setCharacterEncoding("UTF-8"); + response.addHeader(CONTENT_DISPOSITION, + "attachment; filename=" + optional.get().getCode() + "_saml2_metadata_.xml"); + response.getWriter().print(metadataXml); + } + + /** + * getParseSaml2MetadataResult + * + * @param entityDescriptor {@link EntityDescriptor} + * @return {@link ParseSaml2MetadataResult} + */ + public static ParseSaml2MetadataResult getParseSaml2MetadataResult(EntityDescriptor entityDescriptor) { + ParseSaml2MetadataResult metadataResult = new ParseSaml2MetadataResult(); + SPSSODescriptor spssoDescriptor = entityDescriptor.getSPSSODescriptor(SAML20P_NS); + if (Objects.isNull(spssoDescriptor)) { + return metadataResult; + } + //SP EntityId + metadataResult.setEntityId(entityDescriptor.getEntityID()); + //对 Request 签名进行验证 + Boolean requestsSigned = spssoDescriptor.isAuthnRequestsSigned(); + metadataResult.setAuthnRequestsSigned(requestsSigned); + //是否对断言使用IdP的证书签名 + Boolean wantAssertionsSigned = spssoDescriptor.getWantAssertionsSigned(); + metadataResult.setWantAssertionsSigned(wantAssertionsSigned); + List services = spssoDescriptor.getAssertionConsumerServices(); + for (AssertionConsumerService service : services) { + //默认断言服务 + if (service.isDefault()) { + //默认SSO ACS 地址 + metadataResult.setAcsUrl(service.getLocation()); + //默认 ACS 绑定方式 + metadataResult.setDefaultAcsBinding(service.getBinding()); + } + } + //SSO ACS 为空,说明默认断言服务不存在,取第一个 AssertionConsumerService + if (StringUtils.isBlank(metadataResult.getAcsUrl()) && services.size() >= 1) { + metadataResult.setAcsUrl(services.get(0).getLocation()); + metadataResult.setDefaultAcsBinding(services.get(0).getBinding()); + } + //单点登出 + List singleLogoutServices = spssoDescriptor.getSingleLogoutServices(); + for (SingleLogoutService singleLogoutService : singleLogoutServices) { + metadataResult.setSlsUrl(singleLogoutService.getLocation()); + metadataResult.setSlsBinding(singleLogoutService.getBinding()); + metadataResult.setSloEnabled(Boolean.TRUE); + } + List nameIdFormats = spssoDescriptor.getNameIDFormats(); + // NameIdFormat + for (NameIDFormat idFormat : nameIdFormats) { + metadataResult.setDefaultNameIdFormat(idFormat.getURI()); + } + //签名证书 + List keyDescriptors = spssoDescriptor.getKeyDescriptors(); + for (KeyDescriptor keyDescriptor : keyDescriptors) { + if (keyDescriptor.getUse().equals(SIGNING)) { + KeyInfo keyInfo = keyDescriptor.getKeyInfo(); + //X509 List + for (X509Data x509 : keyInfo.getX509Datas()) { + // x509Certificate + for (org.opensaml.xmlsec.signature.X509Certificate x509Certificate : x509 + .getX509Certificates()) { + String value = x509Certificate.getValue(); + if (!StringUtils.isBlank(value)) { + X509Certificate certificate = CertUtils.loadCertFromString(value); + metadataResult.setSignCert(CertUtils.encodePem(certificate)); + break; + } + } + break; + } + break; + } + } + + return metadataResult; + } + + /** + * SAML2应用配置 + */ + private final ApplicationServiceLoader applicationServiceLoader; + + /** + * 应用 + */ + private final AppRepository appRepository; + + /** + * SAML 配置 Repository + */ + private final AppSaml2ConfigRepository appSaml2ConfigRepository; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppServiceImpl.java new file mode 100644 index 00000000..735ca30b --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppServiceImpl.java @@ -0,0 +1,257 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app.impl; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.Optional; + +import org.springframework.data.querydsl.QPageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.Predicate; + +import cn.topiam.employee.application.ApplicationService; +import cn.topiam.employee.application.ApplicationServiceLoader; +import cn.topiam.employee.application.exception.AppNotExistException; +import cn.topiam.employee.audit.context.AuditContext; +import cn.topiam.employee.audit.entity.Target; +import cn.topiam.employee.audit.enums.TargetType; +import cn.topiam.employee.common.entity.app.AppEntity; +import cn.topiam.employee.common.entity.app.QAppEntity; +import cn.topiam.employee.common.repository.app.AppAccessPolicyRepository; +import cn.topiam.employee.common.repository.app.AppAccountRepository; +import cn.topiam.employee.common.repository.app.AppCertRepository; +import cn.topiam.employee.common.repository.app.AppRepository; +import cn.topiam.employee.console.converter.app.AppConverter; +import cn.topiam.employee.console.pojo.query.app.AppQuery; +import cn.topiam.employee.console.pojo.result.app.AppCreateResult; +import cn.topiam.employee.console.pojo.result.app.AppGetResult; +import cn.topiam.employee.console.pojo.result.app.AppListResult; +import cn.topiam.employee.console.pojo.save.app.AppCreateParam; +import cn.topiam.employee.console.pojo.update.app.AppSaveConfigParam; +import cn.topiam.employee.console.pojo.update.app.AppUpdateParam; +import cn.topiam.employee.console.service.app.AppService; +import cn.topiam.employee.support.exception.TopIamException; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.util.BeanUtils; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_BY; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_TIME; + +/** + * ApplicationServiceImpl + * + * @author TopIAM + * Created by support@topiam.cn on 2020/11/29 22:23 + */ +@Service +@Slf4j +@AllArgsConstructor +public class AppServiceImpl implements AppService { + + /** + * 获取应用(分页) + * + * @param pageModel {@link PageModel} + * @param query {@link AppQuery} + * @return {@link AppListResult} + */ + @Override + public Page getAppList(PageModel pageModel, AppQuery query) { + //查询条件 + Predicate predicate = appConverter.queryAppListParamConvertToPredicate(query); + OrderSpecifier desc = QAppEntity.appEntity.createTime.desc(); + //分页条件 + QPageRequest request = QPageRequest.of(pageModel.getCurrent(), pageModel.getPageSize(), + desc); + //查询映射 + org.springframework.data.domain.Page list = appRepository.findAll(predicate, + request); + return appConverter.entityConvertToAppListResult(list); + } + + /** + * 创建应用 + * + * @param param {@link AppCreateParam} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public AppCreateResult createApp(AppCreateParam param) { + ApplicationService applicationService = applicationServiceLoader + .getApplicationService(param.getTemplate()); + String appId = applicationService.create(param.getName(), param.getRemark()); + AuditContext.setTarget(Target.builder().id(appId).type(TargetType.APPLICATION).build()); + return new AppCreateResult(appId); + } + + /** + * 修改应用 + * + * @param param {@link AppUpdateParam} + * @return {@link Boolean} + */ + @Override + public boolean updateApp(AppUpdateParam param) { + AppEntity entity = appConverter.appUpdateParamConverterToEntity(param); + Optional optional = appRepository.findById(param.getId()); + if (optional.isEmpty()) { + throw new AppNotExistException(); + } + AppEntity app = optional.get(); + BeanUtils.merge(entity, app, LAST_MODIFIED_TIME, LAST_MODIFIED_BY); + appRepository.save(app); + AuditContext.setTarget( + Target.builder().id(param.getId().toString()).type(TargetType.APPLICATION).build()); + return true; + } + + /** + * 删除应用 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean deleteApp(Long id) { + Optional optional = appRepository.findById(id); + if (optional.isEmpty()) { + AuditContext.setContent("操作失败,应用不存在"); + log.warn(AuditContext.getContent()); + throw new TopIamException(AuditContext.getContent()); + } + applicationServiceLoader.getApplicationService(optional.get().getTemplate()) + .delete(id.toString()); + AuditContext + .setTarget(Target.builder().id(id.toString()).type(TargetType.APPLICATION).build()); + return true; + } + + /** + * 获取单个应用详情 + * + * @param id {@link Long} + * @return {@link AppEntity} + */ + @Override + public AppGetResult getApp(Long id) { + Optional optional = appRepository.findById(id); + if (optional.isPresent()) { + AppEntity entity = optional.get(); + return appConverter.entityConvertToAppResult(entity); + } + return null; + + } + + /** + * 启用应用 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Override + public Boolean enableApp(String id) { + Integer count = appRepository.updateAppStatus(Long.valueOf(id), Boolean.TRUE); + AuditContext.setTarget(Target.builder().id(id).type(TargetType.APPLICATION).build()); + return count > 0; + } + + /** + * 禁用应用 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Override + public Boolean disableApp(String id) { + Integer count = appRepository.updateAppStatus(Long.valueOf(id), Boolean.FALSE); + AuditContext.setTarget(Target.builder().id(id).type(TargetType.APPLICATION).build()); + return count > 0; + } + + /** + * 更新应用配置 + * + * @param param {@link AppSaveConfigParam} + * @return {@link Boolean} + */ + @Override + public Boolean saveAppConfig(AppSaveConfigParam param) { + ApplicationService applicationService = applicationServiceLoader + .getApplicationService(param.getTemplate()); + applicationService.saveConfig(param.getId(), param.getConfig()); + AuditContext + .setTarget(Target.builder().id(param.getId()).type(TargetType.APPLICATION).build()); + return true; + } + + /** + * 获取应用配置 + * + * @param appId {@link String} + * @return {@link Map} + */ + @Override + public Object getAppConfig(String appId) { + Optional optional = appRepository.findById(Long.valueOf(appId)); + if (optional.isPresent()) { + ApplicationService applicationService = applicationServiceLoader + .getApplicationService(optional.get().getTemplate()); + return applicationService.getConfig(appId); + } + throw new AppNotExistException(); + } + + /** + * ApplicationTemplateLoader + */ + private final ApplicationServiceLoader applicationServiceLoader; + + /** + * ApplicationRepository + */ + private final AppRepository appRepository; + + /** + * 应用证书 + */ + private final AppCertRepository appCertRepository; + + /** + * 应用账户 + */ + private final AppAccountRepository appAccountRepository; + /** + * 应用策略 + */ + private final AppAccessPolicyRepository appAccessPolicyRepository; + + /** + * ApplicationConverter + */ + private final AppConverter appConverter; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppTemplateServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppTemplateServiceImpl.java new file mode 100644 index 00000000..be8fd66f --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/AppTemplateServiceImpl.java @@ -0,0 +1,92 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app.impl; + +import java.util.*; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import cn.topiam.employee.application.ApplicationService; +import cn.topiam.employee.application.ApplicationServiceLoader; +import cn.topiam.employee.common.enums.app.AppType; +import cn.topiam.employee.console.pojo.result.app.AppTemplateResult; +import cn.topiam.employee.console.service.app.AppTemplateService; + +import lombok.AllArgsConstructor; + +/** + * ApplicationTemplateServiceImpl + * + * @author TopIAM + * Created by support@topiam.cn on 2020/11/29 22:23 + */ +@Service +@AllArgsConstructor +public class AppTemplateServiceImpl implements AppTemplateService { + + /** + * List + * + * @param type {@link AppType} + * @return {@link List} + */ + @Override + public List getAppTemplateList(AppType type, String name) { + List results = new ArrayList<>(); + Set list = applicationServiceLoader.getApplicationServiceList(); + if (StringUtils.isNotBlank(name)) { + list = list.stream() + .filter(applicationService -> applicationService.getName().contains(name)) + .collect(Collectors.toSet()); + } + for (ApplicationService protocol : list) { + if (protocol.getType().equals(type)) { + AppTemplateResult result = new AppTemplateResult(); + result.setProtocol(protocol.getProtocol()); + result.setCode(protocol.getCode()); + result.setDesc(protocol.getDescription()); + result.setIcon(protocol.getBase64Icon()); + result.setName(protocol.getName()); + result.setType(protocol.getType()); + results.add(result); + } + } + return results; + } + + /** + * List + * + * @param code {@link Map} + * @return {@link List} + */ + @Override + public List getAppTemplateFormSchema(String code) { + ApplicationService applicationService = applicationServiceLoader + .getApplicationService(code); + if (!Objects.isNull(applicationService)) { + return applicationService.getFormSchema(); + } + return new ArrayList<>(); + } + + private final ApplicationServiceLoader applicationServiceLoader; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/UserIdpBindServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/UserIdpBindServiceImpl.java new file mode 100644 index 00000000..4aedbe96 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/impl/UserIdpBindServiceImpl.java @@ -0,0 +1,90 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app.impl; + +import java.util.List; +import java.util.Optional; + +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.audit.context.AuditContext; +import cn.topiam.employee.audit.entity.Target; +import cn.topiam.employee.audit.enums.TargetType; +import cn.topiam.employee.common.entity.account.UserIdpBindEntity; +import cn.topiam.employee.common.repository.account.UserIdpRepository; +import cn.topiam.employee.console.converter.app.UserIdpBindConverter; +import cn.topiam.employee.console.pojo.result.app.UserIdpBindListResult; +import cn.topiam.employee.console.service.app.UserIdpBindService; +import cn.topiam.employee.support.exception.TopIamException; +import cn.topiam.employee.support.repository.page.domain.Page; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 用户身份提供商绑定 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/12/11 21:10 + */ +@Component +@Slf4j +@AllArgsConstructor +public class UserIdpBindServiceImpl implements UserIdpBindService { + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean unbindUserIdpBind(String id) { + Optional optional = userIdpRepository.findById(Long.valueOf(id)); + //用户不存在 + if (optional.isEmpty()) { + AuditContext.setContent("解绑失败,用户身份提供商绑定关系不存在"); + log.warn(AuditContext.getContent()); + throw new TopIamException(AuditContext.getContent()); + } + UserIdpBindEntity bind = optional.get(); + userIdpRepository.deleteById(Long.valueOf(id)); + AuditContext.setTarget( + Target.builder().id(bind.getUserId().toString()).type(TargetType.USER).build(), + Target.builder().id(bind.getIdpId()).type(TargetType.IDENTITY_PROVIDER).build()); + return true; + } + + /** + * 查询用户身份提供商绑定 + * + * @param userId {@link String} + * @return {@link Page} + */ + @Override + public List getUserIdpBindList(String userId) { + //查询映射 + return userIdpBindConverter.userIdpBindEntityConvertToUserIdpBindListResult( + userIdpRepository.getUserIdpBindList(Long.valueOf(userId))); + } + + /** + * UserIdpBindConverter + */ + private final UserIdpBindConverter userIdpBindConverter; + + /** + * UserIdpRepositoryCustomizedImpl + */ + private final UserIdpRepository userIdpRepository; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/app/package-info.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/package-info.java new file mode 100644 index 00000000..985721af --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/app/package-info.java @@ -0,0 +1,18 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.app; \ No newline at end of file diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/authentication/IdentityProviderService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/authentication/IdentityProviderService.java new file mode 100644 index 00000000..8f4190ea --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/authentication/IdentityProviderService.java @@ -0,0 +1,108 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.authentication; + +import java.util.List; + +import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity; +import cn.topiam.employee.common.enums.IdentityProviderType; +import cn.topiam.employee.console.pojo.query.authentication.IdentityProviderListQuery; +import cn.topiam.employee.console.pojo.result.authentication.IdentityProviderCreateResult; +import cn.topiam.employee.console.pojo.result.authentication.IdentityProviderListResult; +import cn.topiam.employee.console.pojo.result.authentication.IdentityProviderResult; +import cn.topiam.employee.console.pojo.save.authentication.IdentityProviderCreateParam; +import cn.topiam.employee.console.pojo.update.authentication.IdpUpdateParam; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; + +/** + *

+ * 身份认证源配置 服务类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-16 + */ +public interface IdentityProviderService { + /** + * 平台是否启用 + * + * @param id {@link String} + * @return {@link Boolean} + */ + Boolean identityProviderIsEnable(String id); + + /** + * 通过平台类型获取 + * + * @param provider {@link IdentityProviderType} + * @return {@link IdentityProviderEntity} + */ + List getByIdentityProvider(IdentityProviderType provider); + + /** + * 认证源列表 + * + * @param pageModel {@link PageModel } + * @param query {@link IdentityProviderListQuery } + * @return {@link List} + */ + Page getIdentityProviderList(PageModel pageModel, + IdentityProviderListQuery query); + + /** + * 认证源详情 + * + * @param id {@link String} + * @return {@link IdentityProviderResult} + */ + IdentityProviderResult getIdentityProvider(String id); + + /** + * 保存认证源 + * + * @param param {@link IdentityProviderCreateParam} + * @return {@link IdentityProviderCreateResult} + */ + IdentityProviderCreateResult createIdp(IdentityProviderCreateParam param); + + /** + * 更改认证源状态 + * + * @param id {@link String} + * @param enabled {@link Boolean} + * @return {@link Boolean} + */ + Boolean updateIdentityProviderStatus(String id, Boolean enabled); + + /** + * 更新身份源 + * + * @param param {@link IdpUpdateParam} + * @return {@link Boolean} + */ + Boolean updateIdentityProvider(IdpUpdateParam param); + + /** + * 删除认证源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + Boolean deleteIdentityProvider(String id); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/authentication/impl/IdentityProviderServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/authentication/impl/IdentityProviderServiceImpl.java new file mode 100644 index 00000000..a6842749 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/authentication/impl/IdentityProviderServiceImpl.java @@ -0,0 +1,213 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.authentication.impl; + +import java.util.List; +import java.util.Optional; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.audit.context.AuditContext; +import cn.topiam.employee.audit.entity.Target; +import cn.topiam.employee.audit.enums.TargetType; +import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity; +import cn.topiam.employee.common.enums.IdentityProviderType; +import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository; +import cn.topiam.employee.console.converter.authentication.IdentityProviderConverter; +import cn.topiam.employee.console.pojo.query.authentication.IdentityProviderListQuery; +import cn.topiam.employee.console.pojo.result.authentication.IdentityProviderCreateResult; +import cn.topiam.employee.console.pojo.result.authentication.IdentityProviderListResult; +import cn.topiam.employee.console.pojo.result.authentication.IdentityProviderResult; +import cn.topiam.employee.console.pojo.save.authentication.IdentityProviderCreateParam; +import cn.topiam.employee.console.pojo.update.authentication.IdpUpdateParam; +import cn.topiam.employee.console.service.authentication.IdentityProviderService; +import cn.topiam.employee.support.context.ApplicationContextHelp; +import cn.topiam.employee.support.exception.TopIamException; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.repository.page.domain.QueryDslRequest; +import cn.topiam.employee.support.util.BeanUtils; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import static cn.topiam.employee.common.constants.ConfigBeanNameConstants.DEFAULT_SECURITY_FILTER_CHAIN; + +/** + *

+ * 认证源配置 服务实现类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-16 + */ +@Slf4j +@Service +@AllArgsConstructor +public class IdentityProviderServiceImpl implements IdentityProviderService { + + /** + * 平台是否启用 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Override + public Boolean identityProviderIsEnable(String id) { + Optional optional = identityProviderRepository + .findById(Long.valueOf(id)); + return optional.isPresent() && optional.get().getEnabled(); + } + + /** + * 通过平台类型获取 + * + * @param provider {@link IdentityProviderType} + * @return {@link IdentityProviderEntity} + */ + @Override + public List getByIdentityProvider(IdentityProviderType provider) { + return identityProviderRepository.findByType(provider); + } + + /** + * 认证源列表 + * + * @param pageModel {@link PageModel } + * @param query {@link IdentityProviderListQuery} + * @return {@link List} + */ + @Override + public Page getIdentityProviderList(PageModel pageModel, + IdentityProviderListQuery query) { + QueryDslRequest request = identityProviderConverter + .queryIdentityProviderListParamConvertToPredicate(query, pageModel); + org.springframework.data.domain.Page list = identityProviderRepository + .findAll(request.getPredicate(), request.getPageRequest()); + return identityProviderConverter.entityConverterToIdentityProviderResult(list); + } + + /** + * 认证源详情 + * + * @param id {@link String} + * @return {@link IdentityProviderResult} + */ + @Override + public IdentityProviderResult getIdentityProvider(String id) { + Optional optional = identityProviderRepository + .findById(Long.valueOf(id)); + if (optional.isPresent()) { + return identityProviderConverter + .entityConverterToIdentityProviderDetailResult(optional.get()); + } + return new IdentityProviderResult(); + } + + /** + * 创建认证源 + * + * @param param {@link IdentityProviderCreateParam} + * @return {@link IdentityProviderCreateResult} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public IdentityProviderCreateResult createIdp(IdentityProviderCreateParam param) { + //转换对象 + IdentityProviderEntity data = identityProviderConverter + .identityProviderCreateParamConverterToEntity(param); + identityProviderRepository.save(data); + ApplicationContextHelp.refresh(DEFAULT_SECURITY_FILTER_CHAIN); + AuditContext.setTarget(Target.builder().id(data.getId().toString()) + .type(TargetType.IDENTITY_PROVIDER).build()); + return IdentityProviderCreateResult.builder().id(String.valueOf(data.getId())) + .type(data.getType()).build(); + } + + /** + * 更新身份验证源 + * + * @param param {@link IdpUpdateParam} + * @return {@link Boolean} + */ + @Override + public Boolean updateIdentityProvider(IdpUpdateParam param) { + Optional optional = identityProviderRepository + .findById(Long.valueOf(param.getId())); + if (optional.isPresent()) { + IdentityProviderEntity entity = optional.get(); + //转换对象 + IdentityProviderEntity data = identityProviderConverter + .identityProviderUpdateParamConverterToEntity(param); + BeanUtils.merge(data, entity); + identityProviderRepository.save(entity); + ApplicationContextHelp.refresh(DEFAULT_SECURITY_FILTER_CHAIN); + AuditContext.setTarget(Target.builder().id(entity.getId().toString()) + .type(TargetType.IDENTITY_PROVIDER).build()); + return true; + } + throw new NullPointerException("系统不存在该身份源"); + } + + /** + * 删除认证源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Override + public Boolean deleteIdentityProvider(String id) { + Optional optional = identityProviderRepository + .findById(Long.valueOf(id)); + //管理员不存在 + if (optional.isEmpty()) { + AuditContext.setContent("删除失败,认证源不存在"); + log.warn(AuditContext.getContent()); + throw new TopIamException(AuditContext.getContent()); + } + identityProviderRepository.deleteById(Long.valueOf(id)); + ApplicationContextHelp.refresh(DEFAULT_SECURITY_FILTER_CHAIN); + AuditContext.setTarget(Target.builder().id(id).type(TargetType.IDENTITY_PROVIDER).build()); + return Boolean.TRUE; + } + + /** + * 更改认证源状态 + * + * @param id {@link String} + * @param enabled {@link Boolean} + * @return {@link Boolean} + */ + @Override + public Boolean updateIdentityProviderStatus(String id, Boolean enabled) { + boolean result = identityProviderRepository.updateIdentityProviderStatus(Long.valueOf(id), + enabled) > 0; + ApplicationContextHelp.refresh(DEFAULT_SECURITY_FILTER_CHAIN); + AuditContext.setTarget(Target.builder().id(id).type(TargetType.IDENTITY_PROVIDER).build()); + return result; + } + + /** + * 身份源身份转换器 + */ + private final IdentityProviderConverter identityProviderConverter; + /** + * 身份源repository + */ + private final IdentityProviderRepository identityProviderRepository; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/IdentitySourceEventRecordService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/IdentitySourceEventRecordService.java new file mode 100644 index 00000000..fef68646 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/IdentitySourceEventRecordService.java @@ -0,0 +1,44 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.identitysource; + +import cn.topiam.employee.console.pojo.query.identity.IdentitySourceEventRecordListQuery; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceEventRecordListResult; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; + +/** + *

+ * 身份源事件记录 服务类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-16 + */ +public interface IdentitySourceEventRecordService { + + /** + * 身份源事件记录 列表 + * + * @param query {@link IdentitySourceEventRecordListQuery} + * @param pageModel {@link PageModel} + * @return {@link IdentitySourceEventRecordListResult} + */ + Page getIdentitySourceEventRecordList(IdentitySourceEventRecordListQuery query, + PageModel pageModel); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/IdentitySourceService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/IdentitySourceService.java new file mode 100644 index 00000000..9c8d6ea3 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/IdentitySourceService.java @@ -0,0 +1,124 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.identitysource; + +import java.util.List; + +import cn.topiam.employee.common.entity.identitysource.IdentitySourceEntity; +import cn.topiam.employee.console.pojo.other.IdentitySourceConfigValidatorParam; +import cn.topiam.employee.console.pojo.query.identity.IdentitySourceListQuery; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceListResult; +import cn.topiam.employee.console.pojo.save.identitysource.IdentitySourceConfigSaveParam; +import cn.topiam.employee.console.pojo.save.identitysource.IdentitySourceCreateParam; +import cn.topiam.employee.console.pojo.save.identitysource.IdentitySourceCreateResult; +import cn.topiam.employee.console.pojo.update.identity.IdentitySourceUpdateParam; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; + +/** + *

+ * 身份源配置 服务类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-16 + */ +public interface IdentitySourceService { + + /** + * 身份源列表 + * + * @param query {@link IdentitySourceListQuery} + * @param pageModel {@link PageModel} + * @return {@link List} + */ + Page getIdentitySourceList(IdentitySourceListQuery query, + PageModel pageModel); + + /** + * 身份源详情 + * + * @param id {@link String} + * @return {@link IdentitySourceEntity} + */ + IdentitySourceEntity getIdentitySource(String id); + + /** + * 创建身份源 + * + * @param param {@link IdentitySourceCreateParam} + * @return {@link IdentitySourceCreateResult} + */ + IdentitySourceCreateResult createIdentitySource(IdentitySourceCreateParam param); + + /** + * 修改身份源 + * + * @param param {@link IdentitySourceUpdateParam} + * @return {@link Boolean} + */ + Boolean updateIdentitySource(IdentitySourceUpdateParam param); + + /** + * 禁用身份源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + Boolean disableIdentitySource(String id); + + /** + * 启用身份源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + Boolean enableIdentitySource(String id); + + /** + * 删除身份源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + Boolean deleteIdentitySource(String id); + + /** + * 保存身份源配置 + * + * @param param {@link IdentitySourceConfigSaveParam} + * @return {@link Boolean} + */ + Boolean saveIdentitySourceConfig(IdentitySourceConfigSaveParam param); + + /** + * 更新身份源策略 + * + * @param id {@link Long} 主键 + * @param strategyConfig {@link String} 策略 + */ + void updateStrategyConfig(Long id, String strategyConfig); + + /** + * 身份源配置验证 + * + * @param param {@link IdentitySourceConfigValidatorParam} + * @return {@link Boolean} + */ + Boolean identitySourceConfigValidator(IdentitySourceConfigValidatorParam param); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/IdentitySourceSyncService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/IdentitySourceSyncService.java new file mode 100644 index 00000000..96186ca2 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/IdentitySourceSyncService.java @@ -0,0 +1,60 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.identitysource; + +import cn.topiam.employee.console.pojo.query.identity.IdentitySourceSyncHistoryListQuery; +import cn.topiam.employee.console.pojo.query.identity.IdentitySourceSyncRecordListQuery; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceSyncHistoryListResult; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceSyncRecordListResult; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; + +/** + * 身份源同步service接口 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/3/16 22:56 + */ +public interface IdentitySourceSyncService { + /** + * 执行身份源同步 + * + * @param id {@link String} 身份源ID + */ + void executeIdentitySourceSync(String id); + + /** + * 查询身份源同步列表 + * + * @param query {@link IdentitySourceSyncHistoryListQuery} + * @param pageModel {@link PageModel} + * @return {@link IdentitySourceSyncHistoryListResult} + */ + Page getIdentitySourceSyncHistoryList(IdentitySourceSyncHistoryListQuery query, + PageModel pageModel); + + /** + * 查询身份源同步详情 + * + * @param query {@link IdentitySourceSyncRecordListQuery} + * @param pageModel {@link PageModel} + * @return {@link IdentitySourceSyncRecordListResult} + */ + Page getIdentitySourceSyncRecordList(IdentitySourceSyncRecordListQuery query, + PageModel pageModel); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/impl/IdentitySourceEventRecordServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/impl/IdentitySourceEventRecordServiceImpl.java new file mode 100644 index 00000000..368b5313 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/impl/IdentitySourceEventRecordServiceImpl.java @@ -0,0 +1,82 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.identitysource.impl; + +import java.time.LocalDateTime; + +import org.springframework.data.querydsl.QPageRequest; +import org.springframework.stereotype.Service; + +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.Predicate; + +import cn.topiam.employee.common.entity.identitysource.IdentitySourceEventRecordEntity; +import cn.topiam.employee.common.entity.identitysource.QIdentitySourceEventRecordEntity; +import cn.topiam.employee.common.repository.identitysource.IdentitySourceEventRecordRepository; +import cn.topiam.employee.console.converter.identitysource.IdentitySourceEventRecordConverter; +import cn.topiam.employee.console.pojo.query.identity.IdentitySourceEventRecordListQuery; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceEventRecordListResult; +import cn.topiam.employee.console.service.identitysource.IdentitySourceEventRecordService; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; + +import lombok.AllArgsConstructor; + +/** + * 身份源事件记录 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/3/16 19:04 + */ +@Service +@AllArgsConstructor +public class IdentitySourceEventRecordServiceImpl implements IdentitySourceEventRecordService { + + /** + * 身份源事件记录 列表 + * + * @param query {@link IdentitySourceEventRecordListQuery} + * @param pageModel {@link PageModel} + * @return {@link IdentitySourceEventRecordListResult} + */ + @Override + public Page getIdentitySourceEventRecordList(IdentitySourceEventRecordListQuery query, + PageModel pageModel) { + //查询条件 + Predicate predicate = identitySourceEventRecordConverter + .queryIdentitySourceEventRecordListQueryConvertToPredicate(query); + //分页条件 + OrderSpecifier desc = QIdentitySourceEventRecordEntity.identitySourceEventRecordEntity.eventTime + .desc(); + //分页条件 + QPageRequest request = QPageRequest.of(pageModel.getCurrent(), pageModel.getPageSize(), + desc); + //查询映射 + org.springframework.data.domain.Page list = identitySourceEventRecordRepository + .findAll(predicate, request); + return identitySourceEventRecordConverter + .entityConvertToIdentitySourceSyncRecordListResult(list); + } + + /** + * 身份源时间记录 + */ + private final IdentitySourceEventRecordRepository identitySourceEventRecordRepository; + + private final IdentitySourceEventRecordConverter identitySourceEventRecordConverter; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/impl/IdentitySourceServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/impl/IdentitySourceServiceImpl.java new file mode 100644 index 00000000..966aec65 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/impl/IdentitySourceServiceImpl.java @@ -0,0 +1,278 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.identitysource.impl; + +import java.util.List; +import java.util.Optional; + +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.alibaba.fastjson2.JSONObject; + +import cn.topiam.employee.audit.context.AuditContext; +import cn.topiam.employee.audit.entity.Target; +import cn.topiam.employee.audit.enums.TargetType; +import cn.topiam.employee.common.constants.AccountConstants; +import cn.topiam.employee.common.entity.identitysource.IdentitySourceEntity; +import cn.topiam.employee.common.repository.identitysource.IdentitySourceRepository; +import cn.topiam.employee.console.converter.identitysource.IdentitySourceConverter; +import cn.topiam.employee.console.pojo.other.IdentitySourceConfigValidatorParam; +import cn.topiam.employee.console.pojo.query.identity.IdentitySourceListQuery; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceListResult; +import cn.topiam.employee.console.pojo.save.authentication.IdentityProviderCreateParam; +import cn.topiam.employee.console.pojo.save.identitysource.IdentitySourceConfigSaveParam; +import cn.topiam.employee.console.pojo.save.identitysource.IdentitySourceCreateParam; +import cn.topiam.employee.console.pojo.save.identitysource.IdentitySourceCreateResult; +import cn.topiam.employee.console.pojo.update.identity.IdentitySourceUpdateParam; +import cn.topiam.employee.console.service.identitysource.IdentitySourceService; +import cn.topiam.employee.identitysource.dingtalk.DingTalkConfig; +import cn.topiam.employee.identitysource.dingtalk.DingTalkConfigValidator; +import cn.topiam.employee.identitysource.feishu.FeiShuConfig; +import cn.topiam.employee.identitysource.feishu.FeiShuConfigValidator; +import cn.topiam.employee.identitysource.wechatwork.WeChatWorkConfig; +import cn.topiam.employee.identitysource.wechatwork.WeChatWorkConfigValidator; +import cn.topiam.employee.support.exception.TopIamException; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.repository.page.domain.QueryDslRequest; +import cn.topiam.employee.support.util.BeanUtils; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_BY; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_TIME; + +/** + *

+ * 身份源配置 服务类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-16 + */ +@Slf4j +@Service +@AllArgsConstructor +@CacheConfig(cacheNames = { AccountConstants.IDS_CACHE_NAME }) +public class IdentitySourceServiceImpl implements IdentitySourceService { + + /** + * 身份源列表 + * + * @param pageModel {@link PageModel} + * @return {@link List} + */ + @Override + public cn.topiam.employee.support.repository.page.domain.Page getIdentitySourceList(IdentitySourceListQuery query, + PageModel pageModel) { + QueryDslRequest request = identitySourceConverter + .queryIdentitySourceListParamConvertToPredicate(query, pageModel); + org.springframework.data.domain.Page list = identitySourceRepository + .findAll(request.getPredicate(), request.getPageRequest()); + return identitySourceConverter.entityConverterToIdentitySourceListResult(list); + } + + /** + * 身份源详情 + * + * @param id {@link String} + * @return {@link IdentitySourceEntity} + */ + @Override + public IdentitySourceEntity getIdentitySource(String id) { + Optional provider = identitySourceRepository + .findById(Long.valueOf(id)); + return provider.orElse(null); + } + + /** + * 保存身份源 + * + * @param param {@link IdentityProviderCreateParam} + * @return {@link IdentitySourceCreateResult} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public IdentitySourceCreateResult createIdentitySource(IdentitySourceCreateParam param) { + IdentitySourceEntity entity = identitySourceConverter.createParamConverterToEntity(param); + identitySourceRepository.save(entity); + AuditContext.setTarget(Target.builder().id(entity.getId().toString()) + .type(TargetType.IDENTITY_SOURCE).build()); + return new IdentitySourceCreateResult(entity.getId().toString()); + } + + /** + * 修改身份源 + * + * @param param {@link IdentitySourceUpdateParam} + * @return {@link Boolean} + */ + @Override + public Boolean updateIdentitySource(IdentitySourceUpdateParam param) { + IdentitySourceEntity source = identitySourceConverter.updateParamConverterToEntity(param); + IdentitySourceEntity entity = getIdentitySource(param.getId()); + BeanUtils.merge(source, entity, LAST_MODIFIED_TIME, LAST_MODIFIED_BY); + identitySourceRepository.save(entity); + AuditContext.setTarget(Target.builder().id(entity.getId().toString()) + .type(TargetType.IDENTITY_SOURCE).build()); + return true; + } + + /** + * 禁用身份源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Override + public Boolean disableIdentitySource(String id) { + Optional optional = identitySourceRepository + .findById(Long.valueOf(id)); + //身份源不存在 + if (optional.isEmpty()) { + AuditContext.setContent("操作失败,身份源不存在"); + log.warn(AuditContext.getContent()); + throw new TopIamException(AuditContext.getContent()); + } + Integer count = identitySourceRepository.updateIdentitySourceStatus(Long.valueOf(id), + Boolean.FALSE); + AuditContext.setTarget(Target.builder().id(id).type(TargetType.IDENTITY_SOURCE).build()); + return count > 0; + } + + /** + * 启用身份源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Override + public Boolean enableIdentitySource(String id) { + Optional optional = identitySourceRepository + .findById(Long.valueOf(id)); + //用户不存在 + if (optional.isEmpty()) { + AuditContext.setContent("操作失败,身份源不存在"); + log.warn(AuditContext.getContent()); + throw new TopIamException(AuditContext.getContent()); + } + Integer count = identitySourceRepository.updateIdentitySourceStatus(Long.valueOf(id), + Boolean.TRUE); + AuditContext.setTarget(Target.builder().id(id).type(TargetType.IDENTITY_SOURCE).build()); + return count > 0; + } + + /** + * 删除身份源 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Override + public Boolean deleteIdentitySource(String id) { + Optional optional = identitySourceRepository + .findById(Long.valueOf(id)); + //用户不存在 + if (optional.isEmpty()) { + AuditContext.setContent("操作失败,身份源不存在"); + log.warn(AuditContext.getContent()); + throw new TopIamException(AuditContext.getContent()); + } + identitySourceRepository.deleteById(Long.valueOf(id)); + AuditContext.setTarget(Target.builder().id(id).type(TargetType.IDENTITY_SOURCE).build()); + return true; + } + + /** + * 保存身份源配置 + * + * @param param {@link IdentitySourceConfigSaveParam} + * @return {@link Boolean} + */ + @Override + public Boolean saveIdentitySourceConfig(IdentitySourceConfigSaveParam param) { + IdentitySourceEntity entity = getIdentitySource(param.getId()); + param.getBasicConfig().putAll(JSONObject.parseObject(entity.getBasicConfig())); + //转换 + IdentitySourceEntity source = identitySourceConverter + .saveConfigParamConverterToEntity(param, entity.getProvider()); + //合并对象 + BeanUtils.merge(source, entity, LAST_MODIFIED_BY, LAST_MODIFIED_TIME); + identitySourceRepository.save(entity); + AuditContext.setTarget(Target.builder().id(entity.getId().toString()) + .type(TargetType.IDENTITY_SOURCE).build()); + return true; + } + + /** + * 更新身份源策略 + * + * @param id {@link Long} 主键 + * @param strategyConfig {@link String} 策略 + */ + @Override + public void updateStrategyConfig(Long id, String strategyConfig) { + identitySourceRepository.updateStrategyConfig(id, strategyConfig); + AuditContext + .setTarget(Target.builder().id(id.toString()).type(TargetType.IDENTITY_SOURCE).build()); + + } + + /** + * 身份源配置验证 + * + * @param param {@link IdentitySourceConfigValidatorParam} + * @return {@link Boolean} + */ + @Override + public Boolean identitySourceConfigValidator(IdentitySourceConfigValidatorParam param) { + switch (param.getProvider()) { + //钉钉 + case DINGTALK: { + DingTalkConfig config = JSONObject.parseObject(param.getConfig().toJSONString(), + DingTalkConfig.class); + return new DingTalkConfigValidator().validate(config); + } + case FEISHU: { + FeiShuConfig config = JSONObject.parseObject(param.getConfig().toJSONString(), + FeiShuConfig.class); + return new FeiShuConfigValidator().validate(config); + } + case WECHAT_WORK: { + WeChatWorkConfig config = JSONObject.parseObject(param.getConfig().toJSONString(), + WeChatWorkConfig.class); + return new WeChatWorkConfigValidator().validate(config); + } + default: { + throw new TopIamException("暂未支持此提供商连接验证"); + } + } + } + + /** + * 身份验证源转换器 + */ + private final IdentitySourceConverter identitySourceConverter; + + /** + * 身份源 + */ + private final IdentitySourceRepository identitySourceRepository; + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/impl/IdentitySourceSyncServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/impl/IdentitySourceSyncServiceImpl.java new file mode 100644 index 00000000..50ade21c --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/identitysource/impl/IdentitySourceSyncServiceImpl.java @@ -0,0 +1,148 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.identitysource.impl; + +import java.time.LocalDateTime; +import java.util.Objects; + +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.data.querydsl.QPageRequest; +import org.springframework.stereotype.Service; + +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.Predicate; + +import cn.topiam.employee.audit.context.AuditContext; +import cn.topiam.employee.audit.entity.Target; +import cn.topiam.employee.common.entity.identitysource.IdentitySourceEntity; +import cn.topiam.employee.common.entity.identitysource.IdentitySourceSyncHistoryEntity; +import cn.topiam.employee.common.entity.identitysource.IdentitySourceSyncRecordEntity; +import cn.topiam.employee.common.entity.identitysource.QIdentitySourceSyncHistoryEntity; +import cn.topiam.employee.common.repository.identitysource.IdentitySourceSyncHistoryRepository; +import cn.topiam.employee.common.repository.identitysource.IdentitySourceSyncRecordRepository; +import cn.topiam.employee.console.converter.identitysource.IdentitySourceSyncConverter; +import cn.topiam.employee.console.pojo.query.identity.IdentitySourceSyncHistoryListQuery; +import cn.topiam.employee.console.pojo.query.identity.IdentitySourceSyncRecordListQuery; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceSyncHistoryListResult; +import cn.topiam.employee.console.pojo.result.identitysource.IdentitySourceSyncRecordListResult; +import cn.topiam.employee.console.service.identitysource.IdentitySourceService; +import cn.topiam.employee.console.service.identitysource.IdentitySourceSyncService; +import cn.topiam.employee.identitysource.core.event.IdentitySourceEventUtils; +import cn.topiam.employee.identitysource.core.exception.IdentitySourceNotExistException; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import static cn.topiam.employee.audit.enums.TargetType.IDENTITY_SOURCE; + +/** + * 同步身份源同步 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/3/16 19:04 + */ +@Slf4j +@Service +@AllArgsConstructor +public class IdentitySourceSyncServiceImpl implements IdentitySourceSyncService { + /** + * 查询身份源同步列表 + * + * @param query {@link IdentitySourceSyncHistoryListQuery} + * @param pageModel {@link PageModel} + * @return {@link IdentitySourceSyncRecordListResult} + */ + @Override + public Page getIdentitySourceSyncHistoryList(IdentitySourceSyncHistoryListQuery query, + PageModel pageModel) { + //查询条件 + Predicate predicate = identitySourceSyncConverter + .queryIdentitySourceSyncHistoryListQueryConvertToPredicate(query); + //分页条件 + OrderSpecifier desc = QIdentitySourceSyncHistoryEntity.identitySourceSyncHistoryEntity.createTime + .desc(); + QPageRequest request = QPageRequest.of(pageModel.getCurrent(), pageModel.getPageSize(), + desc); + //查询映射 + org.springframework.data.domain.Page list = identitySourceSyncHistoryRepository + .findAll(predicate, request); + return identitySourceSyncConverter.entityConvertToIdentitySourceSyncHistoryListResult(list); + } + + /** + * 查询身份源同步详情 + * + * @param query {@link IdentitySourceSyncRecordListQuery} + * @param pageModel {@link PageModel} + * @return {@link IdentitySourceSyncRecordListResult} + */ + @Override + public Page getIdentitySourceSyncRecordList(IdentitySourceSyncRecordListQuery query, + PageModel pageModel) { + //查询条件 + Predicate predicate = identitySourceSyncConverter + .queryIdentitySourceSyncRecordListQueryConvertToPredicate(query); + //分页条件 + QPageRequest request = QPageRequest.of(pageModel.getCurrent(), pageModel.getPageSize()); + //查询映射 + org.springframework.data.domain.Page list = identitySourceSyncRecordRepository + .findAll(predicate, request); + return identitySourceSyncConverter.entityConvertToIdentitySourceSyncRecordListResult(list); + } + + /** + * 执行身份源同步 + * + * @param id {@link String} 身份源ID + */ + @Override + public void executeIdentitySourceSync(String id) { + AuditContext.setTarget(Target.builder().id(id).type(IDENTITY_SOURCE).build()); + IdentitySourceEntity entity = identitySourceService.getIdentitySource(id); + if (!ObjectUtils.isEmpty(entity)) { + if (Objects.isNull(entity.getBasicConfig())) { + throw new NullPointerException("请完善参数配置"); + } + if (!entity.getEnabled()) { + throw new NullPointerException("身份源已禁用"); + } + //发送分布式事件 + IdentitySourceEventUtils.sync(id); + return; + } + throw new IdentitySourceNotExistException(); + } + + /** + * 身份源service + */ + private final IdentitySourceService identitySourceService; + /** + * 身份源同步记录 + */ + private final IdentitySourceSyncHistoryRepository identitySourceSyncHistoryRepository; + /** + * 身份源同步详情 + */ + private final IdentitySourceSyncRecordRepository identitySourceSyncRecordRepository; + /** + * 身份源同步转换 + */ + private final IdentitySourceSyncConverter identitySourceSyncConverter; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/package-info.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/package-info.java new file mode 100644 index 00000000..aafdee5e --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/package-info.java @@ -0,0 +1,22 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +/** + * @author TopIAM + * Created by support@topiam.cn on 2020/7/9 23:04 + */ +package cn.topiam.employee.console.service; \ No newline at end of file diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/AdministratorService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/AdministratorService.java new file mode 100644 index 00000000..8b606396 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/AdministratorService.java @@ -0,0 +1,106 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.setting; + +import cn.topiam.employee.common.enums.CheckValidityType; +import cn.topiam.employee.common.enums.UserStatus; +import cn.topiam.employee.console.pojo.query.setting.AdministratorListQuery; +import cn.topiam.employee.console.pojo.result.setting.AdministratorListResult; +import cn.topiam.employee.console.pojo.result.setting.AdministratorResult; +import cn.topiam.employee.console.pojo.save.setting.AdministratorCreateParam; +import cn.topiam.employee.console.pojo.update.setting.AdministratorUpdateParam; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; + +/** + * 管理员 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/13 23:12 + */ +public interface AdministratorService { + /** + * 查询平台管理员列表 + * + * @param model {@link PageModel} + * @param query {@link AdministratorListQuery} + * @return {@link Page} + */ + Page getAdministratorList(PageModel model, + AdministratorListQuery query); + + /** + * 创建管理员 + * + * @param param {@link AdministratorCreateParam} + * @return {@link Boolean} + */ + Boolean createAdministrator(AdministratorCreateParam param); + + /** + * 修改管理员 + * + * @param param {@link AdministratorUpdateParam} + * @return {@link Boolean} + */ + Boolean updateAdministrator(AdministratorUpdateParam param); + + /** + * 删除管理员 + * + * @param id {@link String} + * @return {@link Boolean} + */ + Boolean deleteAdministrator(String id); + + /** + * 根据ID获取管理员 + * + * @param id {@link String} + * @return {@link AdministratorResult} + */ + AdministratorResult getAdministrator(String id); + + /** + * 更改管理员状态 + * + * @param id {@link String} + * @param status {@link UserStatus} + * @return {@link Boolean} + */ + Boolean updateAdministratorStatus(String id, UserStatus status); + + /** + * 重置管理员密码 + * + * @param id {@link String} + * @param password {@link String} + * @return {@link Boolean} + */ + Boolean resetAdministratorPassword(String id, String password); + + /** + * 参数有效性验证 + * + * @param type {@link CheckValidityType} + * @param value {@link String} + * @param id {@link Long} + * @return {@link Boolean} + */ + Boolean administratorParamCheck(CheckValidityType type, String value, Long id); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/GeoLocationSettingService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/GeoLocationSettingService.java new file mode 100644 index 00000000..dbb35884 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/GeoLocationSettingService.java @@ -0,0 +1,59 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.setting; + +import cn.topiam.employee.common.geo.GeoLocation; +import cn.topiam.employee.console.pojo.result.setting.GeoIpProviderResult; +import cn.topiam.employee.console.pojo.save.setting.GeoIpProviderSaveParam; + +/** + * ip设置接口 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/10/1 21:19 + */ +public interface GeoLocationSettingService extends SettingService { + + /** + * 保存配置 + * + * @param param {@link GeoIpProviderSaveParam} + * @return {@link Boolean} + */ + Boolean saveGeoIpLibrary(GeoIpProviderSaveParam param); + + /** + * 获取配置 + * + * @return {@link GeoIpProviderResult} + */ + GeoIpProviderResult getGeoIpLibrary(); + + /** + * 查询ip详细信息 + * + * @param ip {@link String} + * @return {@link GeoLocation} + */ + GeoLocation getGeoLocation(String ip); + + /** + * 更新库文件 + */ + void downloadDbFile(); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/MailTemplateService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/MailTemplateService.java new file mode 100644 index 00000000..ec6398da --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/MailTemplateService.java @@ -0,0 +1,75 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.setting; + +import java.util.List; + +import cn.topiam.employee.common.entity.setting.MailTemplateEntity; +import cn.topiam.employee.common.enums.MailType; +import cn.topiam.employee.console.pojo.result.setting.EmailTemplateListResult; +import cn.topiam.employee.console.pojo.result.setting.EmailTemplateResult; +import cn.topiam.employee.console.pojo.save.setting.EmailCustomTemplateSaveParam; + +/** + *

+ * 邮件模板 服务类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-13 + */ +public interface MailTemplateService extends SettingService { + /** + * 根据邮件模板类型获取配置 + * + * @param type {@link MailType} + * @return SettingMailTemplateEntity + */ + MailTemplateEntity getEmailTemplateByType(MailType type); + + /** + * 添加邮件模板 + * + * @param type {@link MailType} + * @param param {@link EmailCustomTemplateSaveParam} + * @return SettingMailTemplateEntity + */ + MailTemplateEntity saveCustomEmailTemplate(MailType type, EmailCustomTemplateSaveParam param); + + /** + * 邮件模板详情 + * + * @param templateType {@link MailType} + * @return {@link EmailTemplateResult} + */ + EmailTemplateResult getEmailTemplate(MailType templateType); + + /** + * 获取邮件模板列表 + * + * @return {@link List} + */ + List getEmailTemplateList(); + + /** + * 禁用自定义模块 + * + * @param type {@link MailType} + */ + void disableCustomEmailTemplate(MailType type); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/MessageSettingService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/MessageSettingService.java new file mode 100644 index 00000000..06503a68 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/MessageSettingService.java @@ -0,0 +1,75 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.setting; + +import cn.topiam.employee.console.pojo.result.setting.EmailProviderConfigResult; +import cn.topiam.employee.console.pojo.save.setting.MailProviderSaveParam; +import cn.topiam.employee.console.pojo.save.setting.SmsProviderSaveParam; +import cn.topiam.employee.console.pojo.setting.SmsProviderConfigResult; + +/** + * 消息设置接口 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/10/1 21:19 + */ +public interface MessageSettingService extends SettingService { + /** + * 保存配置 + * + * @param param {@link MailProviderSaveParam} + * @return {@link Boolean} + */ + Boolean saveMailProviderConfig(MailProviderSaveParam param); + + /** + * 保存邮件验证配置 + * + * @param param {@link SmsProviderSaveParam} + * @return {@link Boolean} + */ + Boolean saveSmsProviderConfig(SmsProviderSaveParam param); + + /** + * 禁用短信验证服务 + * + * @return {@link Boolean} + */ + Boolean disableSmsProvider(); + + /** + * 禁用邮件提供商 + * + * @return {@link Boolean} + */ + Boolean disableMailProvider(); + + /** + * 获取邮件提供商配置 + * + * @return {@link EmailProviderConfigResult} + */ + EmailProviderConfigResult getMailProviderConfig(); + + /** + * 获取短信验证服务配置 + * + * @return {@link SmsProviderConfigResult} + */ + SmsProviderConfigResult getSmsProviderConfig(); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/PasswordPolicyService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/PasswordPolicyService.java new file mode 100644 index 00000000..3e3af112 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/PasswordPolicyService.java @@ -0,0 +1,58 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.setting; + +import java.util.List; + +import cn.topiam.employee.console.pojo.result.setting.PasswordPolicyConfigResult; +import cn.topiam.employee.console.pojo.result.setting.WeakPasswordLibListResult; +import cn.topiam.employee.console.pojo.save.setting.PasswordPolicySaveParam; + +/** + *

+ * 密码策略 服务类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-17 + */ +public interface PasswordPolicyService extends SettingService { + + /** + * 获取配置 + * + * @return {@link PasswordPolicyConfigResult} + */ + PasswordPolicyConfigResult getPasswordPolicyConfig(); + + /** + * 保存配置 + * + * @param param {@link PasswordPolicySaveParam} + * @return {@link Boolean} + */ + Boolean savePasswordPolicyConfig(PasswordPolicySaveParam param); + + /** + * 获取弱密码库 + * + * @return {@link List} + */ + List getWeakPasswordLibList(); + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/SecuritySettingService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/SecuritySettingService.java new file mode 100644 index 00000000..194f6d32 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/SecuritySettingService.java @@ -0,0 +1,89 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.setting; + +import cn.topiam.employee.console.pojo.result.setting.SecurityBasicConfigResult; +import cn.topiam.employee.console.pojo.result.setting.SecurityCaptchaConfigResult; +import cn.topiam.employee.console.pojo.result.setting.SecurityMfaConfigResult; +import cn.topiam.employee.console.pojo.save.setting.SecurityBasicSaveParam; +import cn.topiam.employee.console.pojo.save.setting.SecurityCaptchaSaveParam; +import cn.topiam.employee.console.pojo.save.setting.SecurityMfaSaveParam; + +/** + *

+ * 安全设置表 服务类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-10-01 + */ +public interface SecuritySettingService extends SettingService { + + /** + * 获取配置 + * + * @return {@link SecurityBasicConfigResult} + */ + SecurityBasicConfigResult getBasicConfig(); + + /** + * 保存配置 + * + * @param param {@link SecurityBasicSaveParam} + * @return {@link Boolean} + */ + Boolean saveBasicConfig(SecurityBasicSaveParam param); + + /** + * 获取验证码配置 + * + * @return {@link SecurityCaptchaConfigResult} + */ + SecurityCaptchaConfigResult getCaptchaProviderConfig(); + + /** + * 保存行为验证码 + * + * @param param {@link SecurityCaptchaSaveParam} + * @return {@link Boolean} + */ + Boolean saveCaptchaProviderConfig(SecurityCaptchaSaveParam param); + + /** + * 禁用行为验证码 + * + * @return {@link Boolean} + */ + Boolean disableCaptchaProvider(); + + /** + * 获取MFA配置 + * + * @return {@link SecurityMfaConfigResult} + */ + SecurityMfaConfigResult getMfaConfig(); + + /** + * 保存行为验证码 + * + * @param param {@link SecurityMfaSaveParam} + * @return {@link Boolean} + */ + Boolean saveMfaConfig(SecurityMfaSaveParam param); + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/SettingService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/SettingService.java new file mode 100644 index 00000000..757634ae --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/SettingService.java @@ -0,0 +1,60 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.setting; + +import java.util.List; + +import cn.topiam.employee.common.entity.setting.SettingEntity; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2021/11/9 22:30 + */ +public interface SettingService { + /** + * 保存设置 + * + * @param entity {@link SettingEntity} + * @return {@link Boolean} + */ + Boolean saveSetting(SettingEntity entity); + + /** + * 根据KEY获取配置 + * + * @param name {@link String} + * @return {@link SettingEntity} + */ + SettingEntity getSetting(String name); + + /** + * 根据KEY模糊获取配置 + * + * @param name {@link String} + * @return {@link List} + */ + List findByNameLike(String name); + + /** + * 移除配置 + * + * @param name {@link String} + * @return {@link Boolean} + */ + Boolean removeSetting(String name); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/SmsTemplateService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/SmsTemplateService.java new file mode 100644 index 00000000..48d4b809 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/SmsTemplateService.java @@ -0,0 +1,42 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.setting; + +import java.util.List; + +import cn.topiam.employee.common.enums.Language; +import cn.topiam.employee.console.pojo.result.setting.SmsTemplateListResult; + +/** + *

+ * 短信模版 服务类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-13 + */ +public interface SmsTemplateService extends SettingService { + + /** + * 获取短信模版列表 + * + * @param language {@link Language} + * @return {@link SmsTemplateListResult} + */ + List getSmsTemplateList(Language language); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/StorageSettingService.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/StorageSettingService.java new file mode 100644 index 00000000..72079c25 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/StorageSettingService.java @@ -0,0 +1,51 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.setting; + +import cn.topiam.employee.console.pojo.result.setting.StorageProviderConfigResult; +import cn.topiam.employee.console.pojo.save.setting.StorageConfigSaveParam; + +/** + * 存储设置接口 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/10/1 21:19 + */ +public interface StorageSettingService extends SettingService { + /** + * 更改存储启用禁用 + * + * @return {@link Boolean} + */ + Boolean disableStorage(); + + /** + * 保存存储配置 + * + * @param param {@link StorageConfigSaveParam} + * @return {@link Boolean} + */ + Boolean saveStorageConfig(StorageConfigSaveParam param); + + /** + * 获取存储配置 + * + * @return {@link StorageProviderConfigResult} + */ + StorageProviderConfigResult getStorageConfig(); +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/AdministratorServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/AdministratorServiceImpl.java new file mode 100644 index 00000000..018d1844 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/AdministratorServiceImpl.java @@ -0,0 +1,318 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.setting.impl; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.Executor; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.querydsl.QPageRequest; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.session.Session; +import org.springframework.session.security.SpringSessionBackedSessionRegistry; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.BooleanExpression; + +import cn.topiam.employee.audit.context.AuditContext; +import cn.topiam.employee.audit.entity.Target; +import cn.topiam.employee.audit.enums.TargetType; +import cn.topiam.employee.common.entity.setting.AdministratorEntity; +import cn.topiam.employee.common.entity.setting.QAdministratorEntity; +import cn.topiam.employee.common.enums.CheckValidityType; +import cn.topiam.employee.common.enums.UserStatus; +import cn.topiam.employee.common.repository.setting.AdministratorRepository; +import cn.topiam.employee.console.converter.setting.AdministratorConverter; +import cn.topiam.employee.console.pojo.query.setting.AdministratorListQuery; +import cn.topiam.employee.console.pojo.result.setting.AdministratorListResult; +import cn.topiam.employee.console.pojo.result.setting.AdministratorResult; +import cn.topiam.employee.console.pojo.save.setting.AdministratorCreateParam; +import cn.topiam.employee.console.pojo.update.setting.AdministratorUpdateParam; +import cn.topiam.employee.console.service.setting.AdministratorService; +import cn.topiam.employee.core.security.session.SessionDetails; +import cn.topiam.employee.core.security.session.TopIamSessionBackedSessionRegistry; +import cn.topiam.employee.core.security.util.SecurityUtils; +import cn.topiam.employee.support.exception.InfoValidityFailException; +import cn.topiam.employee.support.exception.TopIamException; +import cn.topiam.employee.support.repository.page.domain.Page; +import cn.topiam.employee.support.repository.page.domain.PageModel; +import cn.topiam.employee.support.util.BeanUtils; + +import lombok.extern.slf4j.Slf4j; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_BY; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_TIME; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2021/11/13 23:13 + */ +@Slf4j +@Service +public class AdministratorServiceImpl implements AdministratorService { + + /** + * 查询平台管理员列表 + * + * @param model {@link PageModel} + * @param query {@link AdministratorListQuery} + * @return {@link List} + */ + @Override + public Page getAdministratorList(PageModel model, + AdministratorListQuery query) { + Predicate predicate = administratorConverter + .queryAdministratorListParamConvertToPredicate(query); + //分页条件 + QPageRequest request = QPageRequest.of(model.getCurrent(), model.getPageSize()); + org.springframework.data.domain.Page page = administratorRepository + .findAll(predicate, request); + return administratorConverter.entityConvertToAdministratorPaginationResult(page); + } + + /** + * 创建管理员 + * + * @param param {@link AdministratorCreateParam} + * @return {@link Boolean} + */ + @Override + public Boolean createAdministrator(AdministratorCreateParam param) { + // 判断用户名、手机号、邮箱是否存在 + Boolean validityPhone = administratorParamCheck(CheckValidityType.PHONE, param.getPhone(), + null); + if (!validityPhone) { + throw new InfoValidityFailException("手机号已存在"); + } + Boolean validityEmail = administratorParamCheck(CheckValidityType.EMAIL, param.getEmail(), + null); + if (!validityEmail) { + throw new InfoValidityFailException("邮箱已存在"); + } + Boolean validityUsername = administratorParamCheck(CheckValidityType.USERNAME, + param.getUsername(), null); + if (!validityUsername) { + throw new InfoValidityFailException("用户名已存在"); + } + AdministratorEntity entity = administratorConverter + .administratorCreateParamConvertToEntity(param); + administratorRepository.save(entity); + AuditContext.setTarget( + Target.builder().id(entity.getId().toString()).type(TargetType.ADMINISTRATOR).build()); + return true; + } + + /** + * 修改管理员 + * + * @param param {@link AdministratorUpdateParam} + * @return {@link Boolean} + */ + @Override + public Boolean updateAdministrator(AdministratorUpdateParam param) { + AdministratorEntity source = administratorConverter + .administratorUpdateParamConvertToEntity(param); + AdministratorEntity target = administratorRepository.findById(Long.valueOf(param.getId())) + .orElse(new AdministratorEntity()); + AuditContext.setContent(source.getUsername()); + BeanUtils.merge(source, target, LAST_MODIFIED_TIME, LAST_MODIFIED_BY); + administratorRepository.save(target); + AuditContext.setTarget( + Target.builder().id(target.getId().toString()).type(TargetType.ADMINISTRATOR).build()); + return true; + } + + /** + * 删除管理员 + * + * @param id {@link String} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean deleteAdministrator(String id) { + Optional optional = administratorRepository.findById(Long.valueOf(id)); + //管理员不存在 + if (optional.isEmpty()) { + AuditContext.setContent("删除失败,管理员不存在"); + log.warn(AuditContext.getContent()); + throw new TopIamException("操作失败"); + } + long count = administratorRepository.count(); + if (count == 1) { + AuditContext.setContent("禁止删除,系统必须存在一个管理员"); + log.warn(AuditContext.getContent()); + throw new TopIamException("操作失败"); + } + //执行删除 + administratorRepository.deleteById(Long.valueOf(id)); + AuditContext + .setTarget(Target.builder().id(id.toString()).type(TargetType.ADMINISTRATOR).build()); + return true; + } + + /** + * 更改管理员状态 + * + * @param id {@link String} + * @param status {@link UserStatus} + * @return {@link Boolean} + */ + @Override + public Boolean updateAdministratorStatus(String id, UserStatus status) { + Optional optional = administratorRepository.findById(Long.valueOf(id)); + optional.ifPresent( + administratorEntity -> AuditContext.setContent(administratorEntity.getUsername())); + long count = administratorRepository.count(); + if (count == 1 && !status.equals(UserStatus.ENABLE)) { + log.warn("禁止删除,当前系统只存在一个管理员"); + throw new RuntimeException("操作失败"); + } + administratorRepository.updateStatus(id, status.getCode()); + AuditContext.setTarget(Target.builder().id(id).type(TargetType.ADMINISTRATOR).build()); + return true; + } + + /** + * 重置管理员密码 + * + * @param id {@link String} + * @param password {@link String} + * @return {@link Boolean} + */ + @Override + public Boolean resetAdministratorPassword(String id, String password) { + password = new String( + Base64.getUrlDecoder().decode(password.getBytes(StandardCharsets.UTF_8)), + StandardCharsets.UTF_8); + password = passwordEncoder.encode(password); + administratorRepository.updatePassword(id, password); + AuditContext.setTarget(Target.builder().id(id).type(TargetType.ADMINISTRATOR).build()); + //异步下线所有用户 + String username = SecurityUtils.getCurrentUserName(); + executor.execute(() -> { + //@formatter:off + if (sessionRegistry instanceof TopIamSessionBackedSessionRegistry) { + List principals = ((TopIamSessionBackedSessionRegistry) sessionRegistry).getPrincipals(username); + principals.forEach(i -> { + if (i instanceof SessionDetails) { + sessionRegistry.removeSessionInformation(((SessionDetails) i).getSessionId()); + } + }); + } + }); + return true; + } + + /** + * 参数有效性验证 + * + * @param type {@link CheckValidityType} + * @param value {@link String} + * @param id {@link Long} + * @return {@link Boolean} false 不可用 true 可用 + */ + @Override + public Boolean administratorParamCheck(CheckValidityType type, String value, Long id) { + QAdministratorEntity administrator = QAdministratorEntity.administratorEntity; + AdministratorEntity entity = new AdministratorEntity(); + boolean result = false; + // ID存在说明是修改操作,查询一下当前数据 + if (Objects.nonNull(id)) { + entity = administratorRepository.findById(id).orElse(new AdministratorEntity()); + } + //邮箱 + if (CheckValidityType.EMAIL.equals(type)) { + if (StringUtils.equals(entity.getEmail(), value)) { + return true; + } + BooleanExpression eq = administrator.email.eq(value); + result = !administratorRepository.exists(eq); + } + //手机号 + if (CheckValidityType.PHONE.equals(type)) { + if (StringUtils.equals(entity.getPhone(), value)) { + return true; + } + BooleanExpression eq = administrator.phone.eq(value); + result = !administratorRepository.exists(eq); + } + //用户名 + if (CheckValidityType.USERNAME.equals(type)) { + if (StringUtils.equals(entity.getUsername(), value)) { + return true; + } + BooleanExpression eq = administrator.username.eq(value); + result = !administratorRepository.exists(eq); + } + return result; + } + + /** + * 查询管理员详情 + * + * @param id {@link String} + * @return {@link AdministratorResult} + */ + @Override + public AdministratorResult getAdministrator(String id) { + AdministratorEntity entity = administratorRepository.findById(Long.valueOf(id)) + .orElse(null); + return administratorConverter.entityConvertToAdministratorDetailsResult(entity); + } + + + /** + * Executor + */ + private final Executor executor; + + /** + * AdministratorConverter + */ + private final AdministratorConverter administratorConverter; + + /** + * AdministratorRepository + */ + private final AdministratorRepository administratorRepository; + + /** + * PasswordEncoder + */ + private final PasswordEncoder passwordEncoder; + + /** + * SessionRegistry + */ + private final SpringSessionBackedSessionRegistry sessionRegistry; + + public AdministratorServiceImpl(AdministratorConverter administratorConverter, AdministratorRepository administratorRepository, PasswordEncoder passwordEncoder, AsyncConfigurer asyncConfigurer, SpringSessionBackedSessionRegistry sessionRegistry) { + this.administratorConverter = administratorConverter; + this.administratorRepository = administratorRepository; + this.passwordEncoder = passwordEncoder; + this.executor = asyncConfigurer.getAsyncExecutor(); + this.sessionRegistry = sessionRegistry; + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/GeoLocationSettingServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/GeoLocationSettingServiceImpl.java new file mode 100644 index 00000000..f97f0fab --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/GeoLocationSettingServiceImpl.java @@ -0,0 +1,105 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.setting.impl; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.common.entity.setting.SettingEntity; +import cn.topiam.employee.common.geo.GeoLocation; +import cn.topiam.employee.common.geo.GeoLocationService; +import cn.topiam.employee.common.repository.setting.SettingRepository; +import cn.topiam.employee.console.converter.setting.GeoLocationSettingConverter; +import cn.topiam.employee.console.pojo.result.setting.GeoIpProviderResult; +import cn.topiam.employee.console.pojo.save.setting.GeoIpProviderSaveParam; +import cn.topiam.employee.console.service.setting.GeoLocationSettingService; +import cn.topiam.employee.support.context.ApplicationContextHelp; + +import lombok.extern.slf4j.Slf4j; +import static cn.topiam.employee.common.constants.ConfigBeanNameConstants.GEO_LOCATION; +import static cn.topiam.employee.core.setting.constant.GeoIpProviderConstants.IPADDRESS_SETTING_NAME; + +/** + * ip设置接口 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/1 21:43 + */ +@Slf4j +@Service +public class GeoLocationSettingServiceImpl extends SettingServiceImpl + implements GeoLocationSettingService { + + /** + * 保存配置 + * + * @param param {@link GeoIpProviderSaveParam} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean saveGeoIpLibrary(GeoIpProviderSaveParam param) { + SettingEntity settingEntity = geoLocationSettingsConverter + .geoLocationProviderConfigToEntity(param); + Boolean success = saveSetting(settingEntity); + downloadDbFile(); + ApplicationContextHelp.refresh(GEO_LOCATION); + return success; + } + + /** + * 获取配置 + * + * @return {@link SettingEntity} + */ + @Override + public GeoIpProviderResult getGeoIpLibrary() { + return geoLocationSettingsConverter + .entityToGeoLocationProviderConfig(getSetting(IPADDRESS_SETTING_NAME)); + } + + /** + * 查询ip详细信息 + * + * @param ip {@link String} + * @return {@link GeoLocation} + */ + @Override + public GeoLocation getGeoLocation(String ip) { + return geoLocationService.getGeoLocation(ip); + } + + /** + * 下载IP库 + */ + @Override + public void downloadDbFile() { + geoLocationService.download(); + } + + private final GeoLocationService geoLocationService; + private final GeoLocationSettingConverter geoLocationSettingsConverter; + + public GeoLocationSettingServiceImpl(SettingRepository settingsRepository, + GeoLocationService geoLocationService, + GeoLocationSettingConverter geoLocationSettingsConverter) { + super(settingsRepository); + this.geoLocationService = geoLocationService; + this.geoLocationSettingsConverter = geoLocationSettingsConverter; + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/MailTemplateServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/MailTemplateServiceImpl.java new file mode 100644 index 00000000..6b00820f --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/MailTemplateServiceImpl.java @@ -0,0 +1,173 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.setting.impl; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.audit.context.AuditContext; +import cn.topiam.employee.audit.entity.Target; +import cn.topiam.employee.audit.enums.TargetType; +import cn.topiam.employee.common.entity.setting.MailTemplateEntity; +import cn.topiam.employee.common.enums.MailType; +import cn.topiam.employee.common.repository.setting.MailTemplateRepository; +import cn.topiam.employee.common.repository.setting.SettingRepository; +import cn.topiam.employee.console.converter.setting.MailTemplateConverter; +import cn.topiam.employee.console.pojo.result.setting.EmailTemplateListResult; +import cn.topiam.employee.console.pojo.result.setting.EmailTemplateResult; +import cn.topiam.employee.console.pojo.save.setting.EmailCustomTemplateSaveParam; +import cn.topiam.employee.console.service.setting.MailTemplateService; +import cn.topiam.employee.support.util.BeanUtils; +import static cn.topiam.employee.core.setting.constant.MessageSettingConstants.SETTING_EMAIL_TEMPLATE_CACHE_NAME; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_BY; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_TIME; + +/** + *

+ * 邮件模板 服务实现类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-13 + */ +@Service +@CacheConfig(cacheNames = SETTING_EMAIL_TEMPLATE_CACHE_NAME) +public class MailTemplateServiceImpl extends SettingServiceImpl implements MailTemplateService { + + /** + * Logger + */ + private final Logger logger = LoggerFactory.getLogger(MailTemplateServiceImpl.class); + + /** + * 根据邮件模板类型获取配置 + * + * @param type {@link MailType} + * @return SettingMailTemplateEntity + */ + @Override + @Cacheable(key = "#result.type.code", unless = "#result == null") + public MailTemplateEntity getEmailTemplateByType(MailType type) { + return mailTemplateRepository.findByType(type); + } + + /** + * 添加邮件模板 + * + * @param type {@link MailType} + * @return SettingMailTemplateEntity + */ + @Override + @Transactional(rollbackFor = Exception.class) + @CachePut(key = "#result.type.code", unless = "#result == null") + public MailTemplateEntity saveCustomEmailTemplate(MailType type, + EmailCustomTemplateSaveParam param) { + //入参映射为实体 + MailTemplateEntity entity = messageDataConverter + .emailTemplateConfigSaveParamConvertToEntity(param); + entity.setType(type); + //查询数据库 + MailTemplateEntity template = getEmailTemplateByType(type); + if (!Objects.isNull(template)) { + BeanUtils.merge(entity, template, LAST_MODIFIED_BY, LAST_MODIFIED_TIME); + return mailTemplateRepository.save(template); + } + AuditContext + .setTarget(Target.builder().id(type.getCode()).type(TargetType.MAIL_TEMPLATE).build()); + return mailTemplateRepository.save(entity); + } + + /** + * 邮件模板详情 + * + * @param templateType {@link MailType} + * @return {@link EmailTemplateResult} + */ + @Override + public EmailTemplateResult getEmailTemplate(MailType templateType) { + //构建查询条件 + //查询 + MailTemplateEntity template = getEmailTemplateByType(templateType); + //如果数据库为空,查找默认模板 + EmailTemplateResult result; + if (Objects.isNull(template)) { + result = messageDataConverter + .mailTemplateTypeConvertToEmailTemplateDetailResult(templateType); + } + //查询数据库 + else { + result = messageDataConverter.entityConvertToEmailTemplateDetailResult(template); + } + return result; + } + + /** + * 获取邮件模板列表 + * 系统自带邮件模板及内容存入枚举, + * 如果用户进行了自定义模板内容,将会把自定义的存入数据库,使用用户自定义的 + * + * @return {@link List} + */ + @Override + public List getEmailTemplateList() { + //从枚举类获取信息 + List values = Arrays.asList(MailType.values()); + //从数据库获取 + List lists = (List) mailTemplateRepository + .findAll(); + return messageDataConverter.mailTemplateTypeConvertToEmailTemplateListResult(values, lists); + } + + /** + * 禁用自定义模块 + * + * @param type {@link MailType} + */ + @Override + @CacheEvict(key = "#type.code") + public void disableCustomEmailTemplate(MailType type) { + mailTemplateRepository.deleteByType(type); + } + + /** + * 消息服务数据映射 + */ + private final MailTemplateConverter messageDataConverter; + /** + * SettingMailTemplateRepository + */ + private final MailTemplateRepository mailTemplateRepository; + + public MailTemplateServiceImpl(SettingRepository settingsRepository, + MailTemplateConverter messageDataConverter, + MailTemplateRepository mailTemplateRepository) { + super(settingsRepository); + this.messageDataConverter = messageDataConverter; + this.mailTemplateRepository = mailTemplateRepository; + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/MessageSettingServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/MessageSettingServiceImpl.java new file mode 100644 index 00000000..35e6bade --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/MessageSettingServiceImpl.java @@ -0,0 +1,128 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.setting.impl; + +import org.springframework.stereotype.Service; + +import cn.topiam.employee.common.entity.setting.SettingEntity; +import cn.topiam.employee.common.repository.setting.SettingRepository; +import cn.topiam.employee.console.converter.setting.MessageSettingConverter; +import cn.topiam.employee.console.pojo.result.setting.EmailProviderConfigResult; +import cn.topiam.employee.console.pojo.save.setting.MailProviderSaveParam; +import cn.topiam.employee.console.pojo.save.setting.SmsProviderSaveParam; +import cn.topiam.employee.console.pojo.setting.SmsProviderConfigResult; +import cn.topiam.employee.console.service.setting.MessageSettingService; +import cn.topiam.employee.support.context.ApplicationContextHelp; +import static cn.topiam.employee.common.constants.ConfigBeanNameConstants.MAIL_PROVIDER_SEND; +import static cn.topiam.employee.common.constants.ConfigBeanNameConstants.SMS_PROVIDER_SEND; +import static cn.topiam.employee.core.setting.constant.MessageSettingConstants.MESSAGE_PROVIDER_EMAIL; +import static cn.topiam.employee.core.setting.constant.MessageSettingConstants.MESSAGE_SMS_PROVIDER; + +/** + * 消息设置 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/10/1 21:19 + */ +@Service +public class MessageSettingServiceImpl extends SettingServiceImpl implements MessageSettingService { + + /** + * 保存配置 + * + * @param param {@link SettingEntity} + * @return {@link Boolean} + */ + @Override + public Boolean saveMailProviderConfig(MailProviderSaveParam param) { + SettingEntity entity = messageSettingConverter.mailProviderConfigToEntity(param); + Boolean setting = saveSetting(entity); + ApplicationContextHelp.refresh(MAIL_PROVIDER_SEND); + return setting; + } + + /** + * 保存邮件验证配置 + * + * @param param {@link SmsProviderSaveParam} + * @return {@link Boolean} + */ + @Override + public Boolean saveSmsProviderConfig(SmsProviderSaveParam param) { + SettingEntity entity = messageSettingConverter.smsProviderConfigToEntity(param); + Boolean setting = saveSetting(entity); + ApplicationContextHelp.refresh(SMS_PROVIDER_SEND); + return setting; + } + + /** + * 禁用短信验证服务 + * + * @return {@link Boolean} + */ + @Override + public Boolean disableSmsProvider() { + Boolean setting = removeSetting(MESSAGE_SMS_PROVIDER); + // refresh + ApplicationContextHelp.refresh(SMS_PROVIDER_SEND); + return setting; + } + + /** + * 禁用邮件提供商 + * + * @return {@link Boolean} + */ + @Override + public Boolean disableMailProvider() { + Boolean setting = removeSetting(MESSAGE_PROVIDER_EMAIL); + // refresh + ApplicationContextHelp.refresh(MAIL_PROVIDER_SEND); + return setting; + } + + /** + * 获取邮件提供商配置 + * + * @return {@link EmailProviderConfigResult} + */ + @Override + public EmailProviderConfigResult getMailProviderConfig() { + SettingEntity entity = getSetting(MESSAGE_PROVIDER_EMAIL); + return messageSettingConverter.entityToMailProviderConfig(entity); + } + + /** + * 获取短信验证服务配置 + * + * @return {@link SmsProviderConfigResult} + */ + @Override + public SmsProviderConfigResult getSmsProviderConfig() { + SettingEntity entity = getSetting(MESSAGE_SMS_PROVIDER); + return messageSettingConverter.entityToSmsProviderConfig(entity); + } + + private final MessageSettingConverter messageSettingConverter; + + public MessageSettingServiceImpl(SettingRepository settingsRepository, + MessageSettingConverter messageSettingConverter) { + super(settingsRepository); + this.messageSettingConverter = messageSettingConverter; + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/PasswordPolicyServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/PasswordPolicyServiceImpl.java new file mode 100644 index 00000000..7c046261 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/PasswordPolicyServiceImpl.java @@ -0,0 +1,120 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.setting.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.common.constants.ConfigBeanNameConstants; +import cn.topiam.employee.common.entity.setting.SettingEntity; +import cn.topiam.employee.common.repository.setting.SettingRepository; +import cn.topiam.employee.console.converter.setting.PasswordPolicyConverter; +import cn.topiam.employee.console.pojo.result.setting.PasswordPolicyConfigResult; +import cn.topiam.employee.console.pojo.result.setting.WeakPasswordLibListResult; +import cn.topiam.employee.console.pojo.save.setting.PasswordPolicySaveParam; +import cn.topiam.employee.console.service.setting.PasswordPolicyService; +import cn.topiam.employee.core.security.password.weak.PasswordWeakLib; +import cn.topiam.employee.support.context.ApplicationContextHelp; +import static cn.topiam.employee.core.setting.constant.PasswordPolicySettingConstants.PASSWORD_POLICY_KEYS; + +/** + *

+ * 密码策略 服务实现类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-17 + */ +@Service +public class PasswordPolicyServiceImpl extends SettingServiceImpl implements PasswordPolicyService { + + /** + * 获取配置 + * + * @return {@link PasswordPolicyConfigResult} + */ + @Override + public PasswordPolicyConfigResult getPasswordPolicyConfig() { + List list = settingRepository.findByNameIn(PASSWORD_POLICY_KEYS); + return passwordPolicyConverter.entityConvertToPasswordPolicyConfigResult(list); + } + + /** + * 保存配置 + * + * @param param {@link PasswordPolicySaveParam} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean savePasswordPolicyConfig(PasswordPolicySaveParam param) { + + //删除密码配置 + PASSWORD_POLICY_KEYS.forEach(settingRepository::deleteByName); + //保存 + List list = passwordPolicyConverter + .passwordPolicySaveParamConvertToEntity(param); + Boolean save = settingRepository.saveConfig(list); + //refresh + ApplicationContextHelp.refresh(ConfigBeanNameConstants.DEFAULT_PASSWORD_POLICY_MANAGER); + return save; + } + + /** + * 获取系统弱密码库 + * + * @return {@link WeakPasswordLibListResult} + */ + @Override + public List getWeakPasswordLibList() { + List results = new ArrayList<>(); + List list = passwordWeakLib.getWordList(); + for (String value : list) { + results.add(new WeakPasswordLibListResult(value)); + } + return results; + } + + /** + * 密码规则转换器 + */ + private final PasswordPolicyConverter passwordPolicyConverter; + + /** + * SettingCipherTacticsRepository + */ + private final SettingRepository settingRepository; + + /** + * PasswordWeakLib + */ + private final PasswordWeakLib passwordWeakLib; + + public PasswordPolicyServiceImpl(SettingRepository settingsRepository, + PasswordPolicyConverter passwordPolicyConverter, + SettingRepository settingRepository, + PasswordWeakLib passwordWeakLib) { + super(settingsRepository); + this.passwordPolicyConverter = passwordPolicyConverter; + this.settingRepository = settingRepository; + this.passwordWeakLib = passwordWeakLib; + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/SecuritySettingServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/SecuritySettingServiceImpl.java new file mode 100644 index 00000000..f87e8874 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/SecuritySettingServiceImpl.java @@ -0,0 +1,203 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.setting.impl; + +import java.util.List; +import java.util.concurrent.Executor; + +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.session.Session; +import org.springframework.session.security.SpringSessionBackedSessionRegistry; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.common.entity.setting.SettingEntity; +import cn.topiam.employee.common.enums.MfaMode; +import cn.topiam.employee.common.repository.setting.SettingRepository; +import cn.topiam.employee.console.converter.setting.SecuritySettingConverter; +import cn.topiam.employee.console.pojo.result.setting.SecurityBasicConfigResult; +import cn.topiam.employee.console.pojo.result.setting.SecurityCaptchaConfigResult; +import cn.topiam.employee.console.pojo.result.setting.SecurityMfaConfigResult; +import cn.topiam.employee.console.pojo.save.setting.SecurityBasicSaveParam; +import cn.topiam.employee.console.pojo.save.setting.SecurityCaptchaSaveParam; +import cn.topiam.employee.console.pojo.save.setting.SecurityMfaSaveParam; +import cn.topiam.employee.console.service.setting.SecuritySettingService; +import cn.topiam.employee.core.security.session.SessionDetails; +import cn.topiam.employee.support.context.ApplicationContextHelp; +import cn.topiam.employee.support.context.ServletContextHelp; +import static cn.topiam.employee.common.constants.ConfigBeanNameConstants.CAPTCHA_VALIDATOR; +import static cn.topiam.employee.common.constants.ConfigBeanNameConstants.DEFAULT_SECURITY_FILTER_CHAIN; +import static cn.topiam.employee.core.setting.constant.MfaSettingConstants.MFA_SETTING_KEYS; +import static cn.topiam.employee.core.setting.constant.SecuritySettingConstants.CAPTCHA_SETTING_NAME; +import static cn.topiam.employee.core.setting.constant.SecuritySettingConstants.SECURITY_BASIC_KEY; + +/** + *

+ * 安全设置表 服务实现类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-10-01 + */ +@Service +public class SecuritySettingServiceImpl extends SettingServiceImpl + implements SecuritySettingService { + + /** + * 获取基础配置 + * + * @return {@link SecurityBasicConfigResult} + */ + @Override + public SecurityBasicConfigResult getBasicConfig() { + //查询数据库配置 + List list = settingRepository.findByNameIn(SECURITY_BASIC_KEY); + return securitySettingConverter.entityConvertToSecurityBasicConfigResult(list); + } + + /** + * 保存基础配置 + * + * @param param {@link SecurityBasicSaveParam} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean saveBasicConfig(SecurityBasicSaveParam param) { + + //删除密码配置 + SECURITY_BASIC_KEY.forEach(settingRepository::deleteByName); + //保存 + List list = securitySettingConverter + .securityBasicSaveParamConvertToEntity(param); + Boolean save = settingRepository.saveConfig(list); + String currentSessionId = ServletContextHelp.getSession().getId(); + //异步下线所有用户(排除当前操作用户) + executor.execute(() -> { + List principals = sessionRegistry.getAllPrincipals(); + principals.forEach(i -> { + if (i instanceof SessionDetails) { + if (!((SessionDetails) i).getSessionId().equals(currentSessionId)) { + sessionRegistry + .removeSessionInformation(((SessionDetails) i).getSessionId()); + } + } + }); + }); + // refresh + ApplicationContextHelp.refresh(DEFAULT_SECURITY_FILTER_CHAIN); + return save; + } + + /** + * 获取验证码配置 + * + * @return {@link SecurityCaptchaConfigResult} + */ + @Override + public SecurityCaptchaConfigResult getCaptchaProviderConfig() { + //查询数据库配置 + SettingEntity entity = settingRepository.findByName(CAPTCHA_SETTING_NAME); + return securitySettingConverter.entityConvertToSecurityCaptchaConfigResult(entity); + } + + /** + * 保存行为验证码 + * + * @param param {@link SecurityCaptchaSaveParam} + * @return {@link Boolean} + */ + @Override + public Boolean saveCaptchaProviderConfig(SecurityCaptchaSaveParam param) { + //保存 + List list = securitySettingConverter + .securityCaptchaSaveParamConvertToEntity(param); + settingRepository.saveConfig(list); + //refresh + ApplicationContextHelp.refresh(CAPTCHA_VALIDATOR); + return true; + } + + /** + * 禁用行为验证码 + * + * @return {@link Boolean} + */ + @Override + public Boolean disableCaptchaProvider() { + Boolean setting = removeSetting(CAPTCHA_SETTING_NAME); + // refresh + ApplicationContextHelp.refresh(CAPTCHA_VALIDATOR); + return setting; + } + + /** + * 获取MFA配置 + * + * @return {@link SecurityMfaConfigResult} + */ + @Override + public SecurityMfaConfigResult getMfaConfig() { + //查询数据库配置 + List list = settingRepository.findByNameIn(MFA_SETTING_KEYS); + return securitySettingConverter.entityConvertToSecurityMfaConfigResult(list); + } + + /** + * 保存MFA + * + * @param param {@link SecurityMfaSaveParam} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean saveMfaConfig(SecurityMfaSaveParam param) { + + if (param.getMode().equals(MfaMode.NONE)) { + settingRepository.deleteByNameIn(MFA_SETTING_KEYS); + return true; + } + //保存 + List list = securitySettingConverter + .securityMfaSaveParamConvertToEntity(param); + return settingRepository.saveConfig(list); + } + + /** + * SecurityBasicDataConverter + */ + private final SecuritySettingConverter securitySettingConverter; + + private final SettingRepository settingRepository; + + private final SpringSessionBackedSessionRegistry sessionRegistry; + + private final Executor executor; + + public SecuritySettingServiceImpl(SettingRepository settingsRepository, + SecuritySettingConverter securitySettingConverter, + SettingRepository settingRepository, + SpringSessionBackedSessionRegistry sessionRegistry, + AsyncConfigurer asyncConfigurer) { + super(settingsRepository); + this.securitySettingConverter = securitySettingConverter; + this.settingRepository = settingRepository; + this.sessionRegistry = sessionRegistry; + this.executor = asyncConfigurer.getAsyncExecutor(); + } +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/SettingServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/SettingServiceImpl.java new file mode 100644 index 00000000..71b0f336 --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/SettingServiceImpl.java @@ -0,0 +1,100 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.setting.impl; + +import java.util.List; +import java.util.Objects; + +import org.springframework.transaction.annotation.Transactional; + +import cn.topiam.employee.common.entity.setting.SettingEntity; +import cn.topiam.employee.common.repository.setting.SettingRepository; +import cn.topiam.employee.console.service.setting.SettingService; +import cn.topiam.employee.support.util.BeanUtils; + +import lombok.AllArgsConstructor; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_BY; +import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_TIME; + +/** + * 设置impl + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/9 22:29 + */ +@AllArgsConstructor +public class SettingServiceImpl implements SettingService { + /** + * 保存设置 + * + * @param entity {@link SettingEntity} + * @return {@link Boolean} + */ + @Override + public Boolean saveSetting(SettingEntity entity) { + SettingEntity setting = getSetting(entity.getName()); + SettingEntity saveEntity = entity; + // 当前配置存在,更改操作 + if (!Objects.isNull(setting)) { + BeanUtils.merge(entity, setting, LAST_MODIFIED_BY, LAST_MODIFIED_TIME); + saveEntity = setting; + } + settingsRepository.save(saveEntity); + return Boolean.TRUE; + } + + /** + * 根据KEY获取配置 + * + * @param name {@link String} + * @return {@link SettingEntity} + */ + @Override + public SettingEntity getSetting(String name) { + return settingsRepository.findByName(name); + } + + /** + * 根据KEY获取配置 + * + * @param name {@link String} + * @return {@link SettingEntity} + */ + @Override + public List findByNameLike(String name) { + return settingsRepository.findByNameLike(name); + } + + /** + * 移除配置 + * + * @param name {@link String} + * @return {@link Boolean} + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean removeSetting(String name) { + settingsRepository.deleteByName(name); + return Boolean.TRUE; + } + + /** + * SettingRepository + */ + private final SettingRepository settingsRepository; +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/SmsTemplateServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/SmsTemplateServiceImpl.java new file mode 100644 index 00000000..baa3938e --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/SmsTemplateServiceImpl.java @@ -0,0 +1,74 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.setting.impl; + +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; + +import org.springframework.stereotype.Service; + +import com.google.common.collect.Lists; + +import cn.topiam.employee.common.enums.Language; +import cn.topiam.employee.common.enums.SmsType; +import cn.topiam.employee.common.repository.setting.SettingRepository; +import cn.topiam.employee.console.pojo.result.setting.SmsTemplateListResult; +import cn.topiam.employee.console.service.setting.SmsTemplateService; + +/** + *

+ * 短信模版模板 服务实现类 + *

+ * + * @author TopIAM + * Created by support@topiam.cn on 2020-08-13 + */ +@Service +public class SmsTemplateServiceImpl extends SettingServiceImpl implements SmsTemplateService { + + /** + * 获取短信模版列表 + * + * @param language {@link Language} + * @return {@link SmsTemplateListResult} + */ + @Override + public List getSmsTemplateList(Language language) { + ResourceBundle resourceBundle = ResourceBundle.getBundle("sms/template/sms-template", + new Locale(language.getLocale())); + List results = Lists.newArrayList(); + SmsType[] values = SmsType.values(); + + for (SmsType type : values) { + SmsTemplateListResult result = new SmsTemplateListResult(); + String content = resourceBundle.getString(type.getCode()); + result.setContent(content); + result.setType(type); + result.setName(type.getDesc()); + result.setLanguage(Language.getType(resourceBundle.getLocale().getLanguage())); + results.add(result); + } + return results; + } + + public SmsTemplateServiceImpl(SettingRepository settingsRepository) { + super(settingsRepository); + } + +} diff --git a/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/StorageSettingServiceImpl.java b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/StorageSettingServiceImpl.java new file mode 100644 index 00000000..fc9174bf --- /dev/null +++ b/eiam-console/src/main/java/cn/topiam/employee/console/service/setting/impl/StorageSettingServiceImpl.java @@ -0,0 +1,86 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package cn.topiam.employee.console.service.setting.impl; + +import org.springframework.stereotype.Service; + +import cn.topiam.employee.common.entity.setting.SettingEntity; +import cn.topiam.employee.common.repository.setting.SettingRepository; +import cn.topiam.employee.console.converter.setting.StorageSettingConverter; +import cn.topiam.employee.console.pojo.result.setting.StorageProviderConfigResult; +import cn.topiam.employee.console.pojo.save.setting.StorageConfigSaveParam; +import cn.topiam.employee.console.service.setting.StorageSettingService; +import cn.topiam.employee.support.context.ApplicationContextHelp; +import static cn.topiam.employee.core.setting.constant.StorageProviderSettingConstants.STORAGE_BEAN_NAME; +import static cn.topiam.employee.core.setting.constant.StorageProviderSettingConstants.STORAGE_PROVIDER_KEY; + +/** + * 存储设置接口 + * + * @author TopIAM + * Created by support@topiam.cn on 2021/11/1 21:43 + */ +@Service +public class StorageSettingServiceImpl extends SettingServiceImpl implements StorageSettingService { + + /** + * 更改存储启用禁用 + * + * @return {@link Boolean} + */ + @Override + public Boolean disableStorage() { + removeSetting(STORAGE_PROVIDER_KEY); + // refresh + ApplicationContextHelp.refresh(STORAGE_BEAN_NAME); + return Boolean.TRUE; + } + + /** + * 保存存储配置 + * + * @param param {@link StorageConfigSaveParam} + * @return {@link Boolean} + */ + @Override + public Boolean saveStorageConfig(StorageConfigSaveParam param) { + SettingEntity entity = storageSettingsConverter.storageConfigSaveParamToEntity(param); + Boolean setting = saveSetting(entity); + ApplicationContextHelp.refresh(STORAGE_BEAN_NAME); + return setting; + } + + /** + * 获取存储配置 + * + * @return {@link StorageProviderConfigResult} + */ + @Override + public StorageProviderConfigResult getStorageConfig() { + SettingEntity entity = getSetting(STORAGE_PROVIDER_KEY); + return storageSettingsConverter.entityToStorageProviderConfig(entity); + } + + private final StorageSettingConverter storageSettingsConverter; + + public StorageSettingServiceImpl(SettingRepository settingsRepository, + StorageSettingConverter storageSettingsConverter) { + super(settingsRepository); + this.storageSettingsConverter = storageSettingsConverter; + } +} diff --git a/eiam-console/src/main/resources/application.yml b/eiam-console/src/main/resources/application.yml new file mode 100644 index 00000000..6287ceda --- /dev/null +++ b/eiam-console/src/main/resources/application.yml @@ -0,0 +1,162 @@ +# +# eiam-console - Employee Identity and Access Management Program +# Copyright © 2020-2022 TopIAM (support@topiam.cn) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +# spring +spring: + application: + name: TopIAM 企业数字身份管控平台管理端 + jackson: + #日期格式化 + date-format: "yyyy-MM-dd HH:mm:ss" + #设置空如何序列化 + default-property-inclusion: NON_NULL + deserialization: + #允许对象忽略json中不存在的属性 + fail_on_unknown_properties: false + parser: + #允许出现单引号 + allow_single_quotes: true + #允许出现特殊字符和转义符 + allow_unquoted_control_chars: true + serialization: + #忽略无法转换的对象 + fail_on_empty_beans: false + #格式化输出 + indent_output: true + cache: + type: redis + redis: + key-prefix: 'topiam' + servlet: + multipart: + max-file-size: 50MB + max-request-size: 50MB + session: + redis: + namespace: ${spring.cache.redis.key-prefix}:session + flush-mode: immediate + store-type: redis + datasource: + type: com.zaxxer.hikari.HikariDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/eiam_develop?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true + username: 'eiam' + password: '' + hikari: + auto-commit: true + connection-test-query: SELECT 1 + connection-timeout: 30000 + idle-timeout: 30000 + max-lifetime: 1800000 + maximum-pool-size: 15 + minimum-idle: 5 + pool-name: eiam + #reids + redis: + host: localhost + port: 6379 + password: '' + database: 9 + lettuce: + pool: + max-idle: 7 + min-idle: 2 + max-active: 7 + max-wait: -1ms + shutdown-timeout: 200ms + timeout: 5000ms + #elasticsearch + elasticsearch: + uris: "http://localhost:9200" + username: "elastic" + password: "" + socket-timeout: 30000s + connection-timeout: 10000s + data: + jpa: + repositories: + bootstrap-mode: deferred + jpa: + hibernate: + ddl-auto: none + show-sql: true + properties: + hibernate: + show_sql: true + format_sql: true + use_sql_comments: true + jdbc: + batch_size: 500 + batch_versioned_data: true + order_inserts: true + types: + print: + banner: false + main: + banner-mode: off +#日志配置 +logging: + config: classpath:config/logback-spring.xml +server: + port: 1898 + shutdown: graceful + #开启压缩 提高响应速度 减少带宽压力 + compression: + enabled: true + mime-types: text/html,text/xml,text/plain,text/css,application/javascript,application/json,image/svg+xml + min-response-size: 1024 + servlet: + session: + cookie: + #dev环境secure=false + secure: false + #dev环境same-site=lax + same-site: lax + name: topiam-employee-console-cookie + http-only: true + encoding: + charset: UTF-8 + #错误处理 + error: + include-stacktrace: always + include-exception: true + include-message: always + include-binding-errors: always + # undertow + undertow: + threads: + io: 32 + worker: 256 + buffer-size: 1024 +# springdoc +springdoc: + swagger-ui: + csrf: + enabled: true + cookie-name: 'topiam-csrf-cookie' + header-name: 'topiam-csrf' + cache: + disabled: true + writer-with-default-pretty-printer: true +#TopIAM +topiam: + server: + console-public-base-url: + portal-public-base-url: + openapi-public-base-url: + synchronizer-public-base-url: \ No newline at end of file diff --git a/eiam-console/src/main/resources/fe/1098.d7361839.async.js b/eiam-console/src/main/resources/fe/1098.d7361839.async.js new file mode 100644 index 00000000..d8e3baf8 --- /dev/null +++ b/eiam-console/src/main/resources/fe/1098.d7361839.async.js @@ -0,0 +1,18 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +"use strict";(self.webpackChunktopiam_console=self.webpackChunktopiam_console||[]).push([[1098],{28058:function(V,T,r){r.d(T,{Z:function(){return o}});var d=r(1413),M=r(67294),f={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M464 720a48 48 0 1096 0 48 48 0 10-96 0zm16-304v184c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V416c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8zm475.7 440l-416-720c-6.2-10.7-16.9-16-27.7-16s-21.6 5.3-27.7 16l-416 720C56 877.4 71.4 904 96 904h832c24.6 0 40-26.6 27.7-48zm-783.5-27.9L512 239.9l339.8 588.2H172.2z"}}]},name:"warning",theme:"outlined"},O=f,h=r(84089),n=function(C,R){return M.createElement(h.Z,(0,d.Z)((0,d.Z)({},C),{},{ref:R,icon:O}))};n.displayName="WarningOutlined";var o=M.forwardRef(n)},31199:function(V,T,r){var d=r(1413),M=r(91),f=r(85893),O=r(67294),h=r(7772),n=["fieldProps","min","proFieldProps","max"],o=function(C,R){var Z=C.fieldProps,D=C.min,p=C.proFieldProps,s=C.max,t=(0,M.Z)(C,n);return(0,f.jsx)(h.Z,(0,d.Z)({valueType:"digit",fieldProps:(0,d.Z)({min:D,max:s},Z),ref:R,filedConfig:{defaultProps:{width:"100%"}},proFieldProps:p},t))};T.Z=O.forwardRef(o)},86615:function(V,T,r){var d=r(1413),M=r(91),f=r(85893),O=r(22270),h=r(78045),n=r(67294),o=r(90789),g=r(7772),C=["fieldProps","options","radioType","layout","proFieldProps","valueEnum"],R=n.forwardRef(function(s,t){var W=s.fieldProps,F=s.options,I=s.radioType,A=s.layout,x=s.proFieldProps,m=s.valueEnum,L=(0,M.Z)(s,C);return(0,f.jsx)(g.Z,(0,d.Z)((0,d.Z)({valueType:I==="button"?"radioButton":"radio",ref:t,valueEnum:(0,O.h)(m,void 0)},L),{},{fieldProps:(0,d.Z)({options:F,layout:A},W),proFieldProps:x,filedConfig:{customLightMode:!0}}))}),Z=n.forwardRef(function(s,t){var W=s.fieldProps,F=s.children;return(0,f.jsx)(h.ZP,(0,d.Z)((0,d.Z)({},W),{},{ref:t,children:F}))}),D=(0,o.G)(Z,{valuePropName:"checked",ignoreWidth:!0}),p=D;p.Group=R,p.Button=h.ZP.Button,p.displayName="ProFormComponent",T.Z=p},64317:function(V,T,r){var d=r(1413),M=r(91),f=r(85893),O=r(22270),h=r(67294),n=r(66758),o=r(7772),g=["fieldProps","children","params","proFieldProps","mode","valueEnum","request","showSearch","options"],C=["fieldProps","children","params","proFieldProps","mode","valueEnum","request","options"],R=h.forwardRef(function(t,W){var F=t.fieldProps,I=t.children,A=t.params,x=t.proFieldProps,m=t.mode,L=t.valueEnum,z=t.request,G=t.showSearch,$=t.options,w=(0,M.Z)(t,g),N=(0,h.useContext)(n.Z);return(0,f.jsx)(o.Z,(0,d.Z)((0,d.Z)({valueEnum:(0,O.h)(L),request:z,params:A,valueType:"select",filedConfig:{customLightMode:!0},fieldProps:(0,d.Z)({options:$,mode:m,showSearch:G,getPopupContainer:N.getPopupContainer},F),ref:W,proFieldProps:x},w),{},{children:I}))}),Z=h.forwardRef(function(t,W){var F=t.fieldProps,I=t.children,A=t.params,x=t.proFieldProps,m=t.mode,L=t.valueEnum,z=t.request,G=t.options,$=(0,M.Z)(t,C),w=(0,d.Z)({options:G,mode:m||"multiple",labelInValue:!0,showSearch:!0,showArrow:!1,autoClearSearchValue:!0,optionLabelProp:"label"},F),N=(0,h.useContext)(n.Z);return(0,f.jsx)(o.Z,(0,d.Z)((0,d.Z)({valueEnum:(0,O.h)(L),request:z,params:A,valueType:"select",filedConfig:{customLightMode:!0},fieldProps:(0,d.Z)({getPopupContainer:N.getPopupContainer},w),ref:W,proFieldProps:x},$),{},{children:I}))}),D=R,p=Z,s=D;s.SearchSelect=p,s.displayName="ProFormComponent",T.Z=s},52688:function(V,T,r){var d=r(1413),M=r(91),f=r(85893),O=r(67294),h=r(7772),n=["fieldProps","unCheckedChildren","checkedChildren","proFieldProps"],o=O.forwardRef(function(g,C){var R=g.fieldProps,Z=g.unCheckedChildren,D=g.checkedChildren,p=g.proFieldProps,s=(0,M.Z)(g,n);return(0,f.jsx)(h.Z,(0,d.Z)({valueType:"switch",fieldProps:(0,d.Z)({unCheckedChildren:Z,checkedChildren:D},R),ref:C,valuePropName:"checked",proFieldProps:p,filedConfig:{valuePropName:"checked",ignoreWidth:!0}},s))});T.Z=o},5966:function(V,T,r){var d=r(1413),M=r(91),f=r(85893),O=r(7772),h=["fieldProps","proFieldProps"],n=["fieldProps","proFieldProps"],o="text",g=function(D){var p=D.fieldProps,s=D.proFieldProps,t=(0,M.Z)(D,h);return(0,f.jsx)(O.Z,(0,d.Z)({valueType:o,fieldProps:p,filedConfig:{valueType:o},proFieldProps:s},t))},C=function(D){var p=D.fieldProps,s=D.proFieldProps,t=(0,M.Z)(D,n);return(0,f.jsx)(O.Z,(0,d.Z)({valueType:"password",fieldProps:p,proFieldProps:s,filedConfig:{valueType:o}},t))},R=g;R.Password=C,R.displayName="ProFormComponent",T.Z=R},50727:function(V,T,r){var d=r(4942),M=r(97685),f=r(91),O=r(74165),h=r(15861),n=r(1413),o=r(85893),g=r(24969),C=r(97462),R=r(952),Z=r(73964),D=r(26369),p=r(22270),s=r(48171),t=r(60249),W=r(71577),F=r(43825),I=r(60869),A=r(94787),x=r(20059),m=r(67294),L=r(1914),z=["onTableChange","maxLength","formItemProps","recordCreatorProps","rowKey","controlled","defaultValue","onChange","editableFormRef"],G=["record","position","creatorButtonText","newRecordType","parentKey","style"],$=m.createContext(void 0);function w(e){var B=e.children,H=e.record,re=e.position,ne=e.newRecordType,J=e.parentKey,X=(0,m.useContext)($);return m.cloneElement(B,(0,n.Z)((0,n.Z)({},B.props),{},{onClick:function(){var Y=(0,h.Z)((0,O.Z)().mark(function oe(te){var k,q,y,U;return(0,O.Z)().wrap(function(E){for(;;)switch(E.prev=E.next){case 0:return E.next=2,(k=(q=B.props).onClick)===null||k===void 0?void 0:k.call(q,te);case 2:if(U=E.sent,U!==!1){E.next=5;break}return E.abrupt("return");case 5:X==null||(y=X.current)===null||y===void 0||y.addEditRecord(H,{position:re,newRecordType:ne,parentKey:J});case 6:case"end":return E.stop()}},oe)}));function b(oe){return Y.apply(this,arguments)}return b}()}))}function N(e){var B,H,re=(0,Z.YB)(),ne=e.onTableChange,J=e.maxLength,X=e.formItemProps,Y=e.recordCreatorProps,b=e.rowKey,oe=e.controlled,te=e.defaultValue,k=e.onChange,q=e.editableFormRef,y=(0,f.Z)(e,z),U=(0,D.D)(e.value),ee=(0,m.useRef)(),E=(0,m.useRef)();(0,m.useImperativeHandle)(y.actionRef,function(){return ee.current});var ce=(0,I.Z)(function(){return e.value||te||[]},{value:e.value,onChange:e.onChange}),se=(0,M.Z)(ce,2),_=se[0],Pe=se[1],j=m.useMemo(function(){return typeof b=="function"?b:function(c,a){return c[b]||a}},[b]),_e=function(a){if(typeof a=="number"&&!e.name){if(a>=_.length)return a;var l=_&&_[a];return j==null?void 0:j(l,a)}if((typeof a=="string"||a>=_.length)&&e.name){var i=_.findIndex(function(u,v){var P;return(j==null||(P=j(u,v))===null||P===void 0?void 0:P.toString())===(a==null?void 0:a.toString())});return i}return a};(0,m.useImperativeHandle)(q,function(){var c=function(i){var u,v;if(i==null)throw new Error("rowIndex is required");var P=_e(i),K=[e.name,(u=P==null?void 0:P.toString())!==null&&u!==void 0?u:""].flat(1).filter(Boolean);return(v=E.current)===null||v===void 0?void 0:v.getFieldValue(K)},a=function(){var i,u=[e.name].flat(1).filter(Boolean);if(Array.isArray(u)&&u.length===0){var v,P=(v=E.current)===null||v===void 0?void 0:v.getFieldsValue();return Array.isArray(P)?P:Object.keys(P).map(function(K){return P[K]})}return(i=E.current)===null||i===void 0?void 0:i.getFieldValue(u)};return(0,n.Z)((0,n.Z)({},E.current),{},{getRowData:c,getRowsData:a,setRowData:function(i,u){var v,P,K,ie;if(i==null)throw new Error("rowIndex is required");var de=_e(i),Oe=[e.name,(v=de==null?void 0:de.toString())!==null&&v!==void 0?v:""].flat(1).filter(Boolean),ge=((P=E.current)===null||P===void 0||(K=P.getFieldsValue)===null||K===void 0?void 0:K.call(P))||{},Re=(0,x.Z)(ge,Oe,(0,n.Z)((0,n.Z)({},c(i)),u||{}));return(ie=E.current)===null||ie===void 0||ie.setFieldsValue(Re),!0}})}),(0,m.useEffect)(function(){!e.controlled||_.forEach(function(c,a){var l;(l=E.current)===null||l===void 0||l.setFieldsValue((0,d.Z)({},j(c,a),c))},{})},[_,e.controlled]),(0,m.useEffect)(function(){if(e.name){var c;E.current=e==null||(c=e.editable)===null||c===void 0?void 0:c.form}},[(B=e.editable)===null||B===void 0?void 0:B.form,e.name]);var S=Y||{},me=S.record,ae=S.position,Ee=S.creatorButtonText,fe=S.newRecordType,he=S.parentKey,Ce=S.style,De=(0,f.Z)(S,G),ve=ae==="top",Q=(0,m.useMemo)(function(){return J&&J<=(_==null?void 0:_.length)?!1:Y!==!1&&(0,o.jsx)(w,{record:(0,p.h)(me,_==null?void 0:_.length,_)||{},position:ae,parentKey:(0,p.h)(he,_==null?void 0:_.length,_),newRecordType:fe,children:(0,o.jsx)(W.Z,(0,n.Z)((0,n.Z)({type:"dashed",style:(0,n.Z)({display:"block",margin:"10px 0",width:"100%"},Ce),icon:(0,o.jsx)(g.Z,{})},De),{},{children:Ee||re.getMessage("editableTable.action.add","\u6DFB\u52A0\u4E00\u884C\u6570\u636E")}))})},[Y,J,_==null?void 0:_.length]),pe=(0,m.useMemo)(function(){return Q?ve?{components:{header:{wrapper:function(a){var l,i=a.className,u=a.children;return(0,o.jsxs)("thead",{className:i,children:[u,(0,o.jsxs)("tr",{style:{position:"relative"},children:[(0,o.jsx)("td",{colSpan:0,style:{visibility:"hidden"},children:Q}),(0,o.jsx)("td",{style:{position:"absolute",left:0,width:"100%"},colSpan:(l=y.columns)===null||l===void 0?void 0:l.length,children:Q})]})]})}}}}:{tableViewRender:function(a,l){var i,u;return(0,o.jsxs)(o.Fragment,{children:[(i=(u=e.tableViewRender)===null||u===void 0?void 0:u.call(e,a,l))!==null&&i!==void 0?i:l,Q]})}}:{}},[ve,Q]),le=(0,n.Z)({},e.editable),Me=(0,s.J)(function(c,a){var l,i,u;if((l=e.editable)===null||l===void 0||(i=l.onValuesChange)===null||i===void 0||i.call(l,c,a),(u=e.onValuesChange)===null||u===void 0||u.call(e,a,c),e.controlled){var v;e==null||(v=e.onChange)===null||v===void 0||v.call(e,a)}});return((e==null?void 0:e.onValuesChange)||((H=e.editable)===null||H===void 0?void 0:H.onValuesChange)||e.controlled&&(e==null?void 0:e.onChange))&&(le.onValuesChange=Me),(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)($.Provider,{value:ee,children:(0,o.jsx)(L.Z,(0,n.Z)((0,n.Z)((0,n.Z)({search:!1,options:!1,pagination:!1,rowKey:b,revalidateOnFocus:!1},y),pe),{},{tableLayout:"fixed",actionRef:ee,onChange:ne,editable:(0,n.Z)((0,n.Z)({},le),{},{formProps:(0,n.Z)({formRef:E},le.formProps)}),dataSource:_,onDataSourceChange:function(a){if(Pe(a),e.name&&ae==="top"){var l,i=(0,x.Z)({},[e.name].flat(1).filter(Boolean),a);(l=E.current)===null||l===void 0||l.setFieldsValue(i)}}}))}),e.name?(0,o.jsx)(C.Z,{name:[e.name],children:function(a){var l,i,u=(0,A.default)(a,[e.name].flat(1)),v=u==null?void 0:u.find(function(P,K){return!(0,t.A)(P,U==null?void 0:U[K])});return v&&U&&(e==null||(l=e.editable)===null||l===void 0||(i=l.onValuesChange)===null||i===void 0||i.call(l,v,u)),null}}):null]})}function ue(e){var B=R.ZP.useFormInstance();return e.name?(0,o.jsx)(F.Z.Item,(0,n.Z)((0,n.Z)({style:{maxWidth:"100%"}},e==null?void 0:e.formItemProps),{},{name:e.name,children:(0,o.jsx)(N,(0,n.Z)((0,n.Z)({},e),{},{editable:(0,n.Z)((0,n.Z)({},e.editable),{},{form:B})}))})):(0,o.jsx)(N,(0,n.Z)({},e))}ue.RecordCreator=w,T.Z=ue}}]); diff --git a/eiam-console/src/main/resources/fe/1158.74b40769.async.js b/eiam-console/src/main/resources/fe/1158.74b40769.async.js new file mode 100644 index 00000000..7d46d3e4 --- /dev/null +++ b/eiam-console/src/main/resources/fe/1158.74b40769.async.js @@ -0,0 +1,18 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +"use strict";(self.webpackChunktopiam_console=self.webpackChunktopiam_console||[]).push([[1158],{51158:function(qe,oe,c){c.d(oe,{ZM:function(){return T},ZP:function(){return Le}});var J=c(74902),I=c(87462),t=c(4942),A=c(97685),se=c(71002),de=c(94184),$=c.n(de),a=c(67294),w=c(53124),me=c(88258),ce=c(92820),ge=c(25378),pe=c(18749),fe=c(57953),Y=c(24308),ue=c(21584),ve=c(96159),Q=function(r,e){var d={};for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&e.indexOf(n)<0&&(d[n]=r[n]);if(r!=null&&typeof Object.getOwnPropertySymbols=="function")for(var o=0,n=Object.getOwnPropertySymbols(r);o1},G=function(){return P==="vertical"?!!s:!z()},Z=C("list",n),L=l&&l.length>0&&a.createElement("ul",{className:Z+"-item-action",key:"actions"},l.map(function(S,y){return a.createElement("li",{key:Z+"-item-action-"+y},S,y!==l.length-1&&a.createElement("em",{className:Z+"-item-action-split"}))})),M=u?"div":"li",B=a.createElement(M,(0,I.Z)({},x,u?{}:{ref:d},{className:$()(Z+"-item",(0,t.Z)({},Z+"-item-no-flex",!G()),m)}),P==="vertical"&&s?[a.createElement("div",{className:Z+"-item-main",key:"content"},o,L),a.createElement("div",{className:Z+"-item-extra",key:"extra"},s)]:[o,L,(0,ve.Tm)(s,{key:"extra"})]);return u?a.createElement(ue.Z,{ref:d,flex:1,style:i},B):B},U=(0,a.forwardRef)(Se);U.Meta=xe;var he=U,Ze=c(67968),ye=c(45503),Ce=c(14747),Ee=function(e){var d,n,o=e.listBorderedCls,l=e.componentCls,s=e.paddingLG,m=e.margin,i=e.padding,x=e.listItemPaddingSM,f=e.marginLG,u=e.borderRadiusLG;return n={},(0,t.Z)(n,""+o,(d={border:e.lineWidth+"px "+e.lineType+" "+e.colorBorder,borderRadius:u},(0,t.Z)(d,l+"-header,"+l+"-footer,"+l+"-item",{paddingInline:s}),(0,t.Z)(d,l+"-pagination",{margin:m+"px "+f+"px"}),d)),(0,t.Z)(n,""+o+l+"-sm",(0,t.Z)({},l+"-item,"+l+"-header,"+l+"-footer",{padding:x})),(0,t.Z)(n,""+o+l+"-lg",(0,t.Z)({},l+"-item,"+l+"-header,"+l+"-footer",{padding:i+"px "+s+"px"})),n},Ie=function(e){var d,n,o,l,s=e.componentCls,m=e.screenSM,i=e.screenMD,x=e.marginLG,f=e.marginSM,u=e.margin;return l={},(0,t.Z)(l,"@media screen and (max-width:"+i+")",(d={},(0,t.Z)(d,""+s,(0,t.Z)({},s+"-item",(0,t.Z)({},s+"-item-action",{marginInlineStart:x}))),(0,t.Z)(d,s+"-vertical",(0,t.Z)({},s+"-item",(0,t.Z)({},s+"-item-extra",{marginInlineStart:x}))),d)),(0,t.Z)(l,"@media screen and (max-width: "+m+")",(o={},(0,t.Z)(o,""+s,(0,t.Z)({},s+"-item",(0,t.Z)({flexWrap:"wrap"},s+"-action",{marginInlineStart:f}))),(0,t.Z)(o,s+"-vertical",(0,t.Z)({},s+"-item",(n={flexWrap:"wrap-reverse"},(0,t.Z)(n,s+"-item-main",{minWidth:e.contentWidth}),(0,t.Z)(n,s+"-item-extra",{margin:"auto auto "+u+"px"}),n))),o)),l},Pe=function(e){var d,n,o,l,s,m,i=e.componentCls,x=e.antCls,f=e.controlHeight,u=e.minHeight,P=e.paddingSM,p=e.marginLG,C=e.padding,z=e.listItemPadding,G=e.colorPrimary,Z=e.listItemPaddingSM,L=e.listItemPaddingLG,M=e.paddingXS,B=e.margin,S=e.colorText,y=e.colorTextDescription,H=e.motionDurationSlow,D=e.lineWidth;return m={},(0,t.Z)(m,""+i,(0,I.Z)((0,I.Z)({},(0,Ce.Wf)(e)),(l={position:"relative","*":{outline:"none"}},(0,t.Z)(l,i+"-header, "+i+"-footer",{background:"transparent",paddingBlock:P}),(0,t.Z)(l,i+"-pagination",(0,t.Z)({marginBlockStart:p,textAlign:"end"},x+"-pagination-options",{textAlign:"start"})),(0,t.Z)(l,i+"-spin",{minHeight:u,textAlign:"center"}),(0,t.Z)(l,i+"-items",{margin:0,padding:0,listStyle:"none"}),(0,t.Z)(l,i+"-item",(o={display:"flex",alignItems:"center",justifyContent:"space-between",padding:z,color:S},(0,t.Z)(o,i+"-item-meta",(d={display:"flex",flex:1,alignItems:"flex-start",maxWidth:"100%"},(0,t.Z)(d,i+"-item-meta-avatar",{marginInlineEnd:C}),(0,t.Z)(d,i+"-item-meta-content",{flex:"1 0",width:0,color:S}),(0,t.Z)(d,i+"-item-meta-title",{marginBottom:e.marginXXS,color:S,fontSize:e.fontSize,lineHeight:e.lineHeight,"> a":(0,t.Z)({color:S,transition:"all "+H},"&:hover",{color:G})}),(0,t.Z)(d,i+"-item-meta-description",{color:y,fontSize:e.fontSize,lineHeight:e.lineHeight}),d)),(0,t.Z)(o,i+"-item-action",(n={flex:"0 0 auto",marginInlineStart:e.marginXXL,padding:0,fontSize:0,listStyle:"none"},(0,t.Z)(n,"& > li",(0,t.Z)({position:"relative",display:"inline-block",padding:"0 "+M+"px",color:y,fontSize:e.fontSize,lineHeight:e.lineHeight,textAlign:"center"},"&:first-child",{paddingInlineStart:0})),(0,t.Z)(n,i+"-item-action-split",{position:"absolute",insetBlockStart:"50%",insetInlineEnd:0,width:D,height:Math.ceil(e.fontSize*e.lineHeight)-e.marginXXS*2,transform:"translateY(-50%)",backgroundColor:e.colorSplit}),n)),o)),(0,t.Z)(l,i+"-empty",{padding:C+"px 0",color:y,fontSize:e.fontSizeSM,textAlign:"center"}),(0,t.Z)(l,i+"-empty-text",{padding:C,color:e.colorTextDisabled,fontSize:e.fontSize,textAlign:"center"}),(0,t.Z)(l,i+"-item-no-flex",{display:"block"}),l))),(0,t.Z)(m,i+"-grid "+x+"-col > "+i+"-item",{display:"block",maxWidth:"100%",marginBlockEnd:B,paddingBlock:0,borderBlockEnd:"none"}),(0,t.Z)(m,i+"-vertical "+i+"-item",(s={alignItems:"initial"},(0,t.Z)(s,i+"-item-main",{display:"block",flex:1}),(0,t.Z)(s,i+"-item-extra",{marginInlineStart:p}),(0,t.Z)(s,i+"-item-meta",(0,t.Z)({marginBlockEnd:C},i+"-item-meta-title",{marginBlockEnd:P,color:S,fontSize:e.fontSizeLG,lineHeight:e.lineHeightLG})),(0,t.Z)(s,i+"-item-action",{marginBlockStart:C,marginInlineStart:"auto","> li":(0,t.Z)({padding:"0 "+C+"px"},"&:first-child",{paddingInlineStart:0})}),s)),(0,t.Z)(m,i+"-split "+i+"-item",(0,t.Z)({borderBlockEnd:e.lineWidth+"px "+e.lineType+" "+e.colorSplit},"&:last-child",{borderBlockEnd:"none"})),(0,t.Z)(m,i+"-split "+i+"-header",{borderBlockEnd:e.lineWidth+"px "+e.lineType+" "+e.colorSplit}),(0,t.Z)(m,i+"-split"+i+"-empty "+i+"-footer",{borderTop:e.lineWidth+"px "+e.lineType+" "+e.colorSplit}),(0,t.Z)(m,i+"-loading "+i+"-spin-nested-loading",{minHeight:f}),(0,t.Z)(m,i+"-split"+i+"-something-after-last-item "+x+"-spin-container > "+i+"-items > "+i+"-item:last-child",{borderBlockEnd:e.lineWidth+"px "+e.lineType+" "+e.colorSplit}),(0,t.Z)(m,i+"-lg "+i+"-item",{padding:L}),(0,t.Z)(m,i+"-sm "+i+"-item",{padding:Z}),(0,t.Z)(m,i+":not("+i+"-vertical)",(0,t.Z)({},i+"-item-no-flex",(0,t.Z)({},i+"-item-action",{float:"right"}))),m},be=(0,Ze.Z)("List",function(r){var e=(0,ye.TS)(r,{listBorderedCls:r.componentCls+"-bordered",minHeight:r.controlHeightLG,listItemPadding:r.paddingContentVertical+"px "+r.paddingContentHorizontalLG+"px",listItemPaddingSM:r.paddingContentVerticalSM+"px "+r.paddingContentHorizontal+"px",listItemPaddingLG:r.paddingContentVerticalLG+"px "+r.paddingContentHorizontalLG+"px"});return[Pe(e),Ee(e),Ie(e)]},{contentWidth:220}),ze=function(r,e){var d={};for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&e.indexOf(n)<0&&(d[n]=r[n]);if(r!=null&&typeof Object.getOwnPropertySymbols=="function")for(var o=0,n=Object.getOwnPropertySymbols(r);one&&(E.current=ne);var re=n?a.createElement("div",{className:v+"-pagination"},a.createElement(pe.Z,(0,I.Z)({},E,{onChange:Ae,onShowSizeChange:$e}))):null,V=(0,J.Z)(z);n&&z.length>(E.current-1)*E.pageSize&&(V=(0,J.Z)(z).splice((E.current-1)*E.pageSize,E.pageSize));var Ye=Object.keys(p||{}).some(function(g){return["xs","sm","md","lg","xl","xxl"].includes(g)}),ae=(0,ge.Z)(Ye),R=a.useMemo(function(){for(var g=0;g0){var le=V.map(function(g,h){return we(g,h)});K=p?a.createElement(ce.Z,{gutter:p.gutter},a.Children.map(le,function(g){return a.createElement("div",{key:g==null?void 0:g.key,style:Qe},g)})):a.createElement("ul",{className:v+"-items"},le)}else!f&&!F&&(K=Xe(v,We||me.Z));var j=E.position||"bottom",Ue=a.useMemo(function(){return{grid:p,itemLayout:u}},[JSON.stringify(p),u]);return Ve(a.createElement(T.Provider,{value:Ue},a.createElement("div",(0,I.Z)({className:Je},D),(j==="top"||j==="both")&&re,Z&&a.createElement("div",{className:v+"-header"},Z),a.createElement(fe.Z,(0,I.Z)({},O),K,f),L&&a.createElement("div",{className:v+"-footer"},L),P||(j==="bottom"||j==="both")&&re)))}q.Item=he;var Le=q}}]); diff --git a/eiam-console/src/main/resources/fe/137.d372bd41.async.js b/eiam-console/src/main/resources/fe/137.d372bd41.async.js new file mode 100644 index 00000000..9c507cf3 --- /dev/null +++ b/eiam-console/src/main/resources/fe/137.d372bd41.async.js @@ -0,0 +1,18 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +"use strict";(self.webpackChunktopiam_console=self.webpackChunktopiam_console||[]).push([[137],{137:function(or,de,v){v.d(de,{$C:function(){return Xe},AG:function(){return Ke},B9:function(){return De},Cj:function(){return er},Dx:function(){return ze},EJ:function(){return sr},FV:function(){return $e},GX:function(){return Be},Iw:function(){return Je},K8:function(){return He},NK:function(){return xe},Nq:function(){return Le},PR:function(){return Oe},QA:function(){return ur},QV:function(){return Se},S4:function(){return Me},SN:function(){return Ve},Xu:function(){return we},Yi:function(){return be},Zy:function(){return qe},_p:function(){return nr},bh:function(){return Fe},eE:function(){return Re},eU:function(){return ar},gD:function(){return Ze},jA:function(){return tr},kX:function(){return Ge},lE:function(){return Te},lu:function(){return Ie},lz:function(){return rr},mB:function(){return ve},mo:function(){return ir},mz:function(){return je},nv:function(){return Qe},qD:function(){return We},qb:function(){return Ue},r4:function(){return Pe},re:function(){return ke},rl:function(){return ye},rn:function(){return Ye},w0:function(){return Ee},wZ:function(){return Ce},wk:function(){return Ne},xc:function(){return Ae},z3:function(){return ge}});var _e=v(11281),m=v.n(_e),me=v(97983),u=v.n(me),fe=v(40794),s=v.n(fe),f=v(25191),he=v(80129),pe=v.n(he),o=v(98971);function ve(){return g.apply(this,arguments)}function g(){return g=s()(u()().mark(function n(){return u()().wrap(function(a){for(;;)switch(a.prev=a.next){case 0:return a.abrupt("return",(0,o.request)("/api/organization/get_root"));case 1:case"end":return a.stop()}},n)})),g.apply(this,arguments)}function ge(n){return y.apply(this,arguments)}function y(){return y=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/organization/get_child",{params:{parentId:t}}));case 1:case"end":return e.stop()}},n)})),y.apply(this,arguments)}function ye(n){return w.apply(this,arguments)}function w(){return w=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/organization/filter_tree",{params:{keyWord:t}}));case 1:case"end":return e.stop()}},n)})),w.apply(this,arguments)}function we(n){return b.apply(this,arguments)}function b(){return b=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/organization/get/".concat(t)));case 1:case"end":return e.stop()}},n)})),b.apply(this,arguments)}function be(n){return $.apply(this,arguments)}function $(){return $=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/organization/create",{method:"POST",requestType:"json",data:t}));case 1:case"end":return e.stop()}},n)})),$.apply(this,arguments)}function $e(n){return E.apply(this,arguments)}function E(){return E=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/organization/update",{method:"PUT",requestType:"json",data:t}));case 1:case"end":return e.stop()}},n)})),E.apply(this,arguments)}function Ee(n){return U.apply(this,arguments)}function U(){return U=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/organization/delete/".concat(t),{method:"DELETE"}));case 1:case"end":return e.stop()}},n)})),U.apply(this,arguments)}function Ue(n,t){return T.apply(this,arguments)}function T(){return T=s()(u()().mark(function n(t,a){return u()().wrap(function(l){for(;;)switch(l.prev=l.next){case 0:return l.abrupt("return",(0,o.request)("/api/organization/move",{params:{id:t,parentId:a},method:"PUT"}));case 1:case"end":return l.stop()}},n)})),T.apply(this,arguments)}function Te(n,t,a){return P.apply(this,arguments)}function P(){return P=s()(u()().mark(function n(t,a,e){return u()().wrap(function(i){for(;;)switch(i.prev=i.next){case 0:return i.abrupt("return",(0,o.request)("/api/user/list",{params:m()(m()(m()({},t),(0,f.YE)(a)),(0,f.oH)(e))}).then(function(r){var c,p,d,_,h={data:r!=null&&(c=r.result)!==null&&c!==void 0&&c.list?r==null||(p=r.result)===null||p===void 0?void 0:p.list:[],success:r==null?void 0:r.success,total:r!=null&&(d=r.result)!==null&&d!==void 0&&d.pagination?r==null||(_=r.result)===null||_===void 0?void 0:_.pagination.total:0};return Promise.resolve(h)}));case 1:case"end":return i.stop()}},n)})),P.apply(this,arguments)}function Pe(n){return I.apply(this,arguments)}function I(){return I=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/user/create",{method:"POST",requestType:"json",data:t}));case 1:case"end":return e.stop()}},n)})),I.apply(this,arguments)}function Ie(n,t,a){return q.apply(this,arguments)}function q(){return q=s()(u()().mark(function n(t,a,e){return u()().wrap(function(i){for(;;)switch(i.prev=i.next){case 0:return i.abrupt("return",(0,o.request)("/api/user/login_audit/list",{method:"GET",params:m()(m()(m()({},t),(0,f.YE)(a)),(0,f.oH)(e))}).then(function(r){var c,p,d,_,h={data:r!=null&&(c=r.result)!==null&&c!==void 0&&c.list?r==null||(p=r.result)===null||p===void 0?void 0:p.list:[],success:r==null?void 0:r.success,total:r!=null&&(d=r.result)!==null&&d!==void 0&&d.pagination?r==null||(_=r.result)===null||_===void 0?void 0:_.pagination.total:0};return Promise.resolve(h)}));case 1:case"end":return i.stop()}},n)})),q.apply(this,arguments)}function qe(n){return k.apply(this,arguments)}function k(){return k=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/user/idp_bind",{method:"GET",params:m()({},t)}).then(function(l){var i={data:l!=null&&l.result?l==null?void 0:l.result:[],success:l==null?void 0:l.success};return Promise.resolve(i)}));case 1:case"end":return e.stop()}},n)})),k.apply(this,arguments)}function ke(n){return S.apply(this,arguments)}function S(){return S=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/user/unbind_idp",{method:"DELETE",params:{id:t}}));case 1:case"end":return e.stop()}},n)})),S.apply(this,arguments)}function lr(n,t){return O.apply(this,arguments)}function O(){return O=_asyncToGenerator(_regeneratorRuntime().mark(function n(t,a){return _regeneratorRuntime().wrap(function(l){for(;;)switch(l.prev=l.next){case 0:return l.abrupt("return",request("/api/user/transfer",{method:"PUT",params:{userId:t,orgId:a}}));case 1:case"end":return l.stop()}},n)})),O.apply(this,arguments)}function Se(n){return L.apply(this,arguments)}function L(){return L=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/user/reset_password",{method:"PUT",data:t}));case 1:case"end":return e.stop()}},n)})),L.apply(this,arguments)}function Oe(n){return G.apply(this,arguments)}function G(){return G=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/user/get/".concat(t),{method:"GET"}));case 1:case"end":return e.stop()}},n)})),G.apply(this,arguments)}function Le(n){return j.apply(this,arguments)}function j(){return j=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/user/update",{method:"PUT",requestType:"json",data:t}));case 1:case"end":return e.stop()}},n)})),j.apply(this,arguments)}function Ge(n){return z.apply(this,arguments)}function z(){return z=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/user/delete/".concat(t),{method:"DELETE"}));case 1:case"end":return e.stop()}},n)})),z.apply(this,arguments)}function cr(n){return D.apply(this,arguments)}function D(){return D=_asyncToGenerator(_regeneratorRuntime().mark(function n(t){return _regeneratorRuntime().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",request("/api/user/resign/".concat(t),{method:"DELETE"}));case 1:case"end":return e.stop()}},n)})),D.apply(this,arguments)}function je(n,t,a){return R.apply(this,arguments)}function R(){return R=s()(u()().mark(function n(t,a,e){return u()().wrap(function(i){for(;;)switch(i.prev=i.next){case 0:return i.abrupt("return",(0,o.request)("/api/user/param_check",{params:{id:e,type:t,value:a},method:"GET"}));case 1:case"end":return i.stop()}},n)})),R.apply(this,arguments)}function ze(n){return M.apply(this,arguments)}function M(){return M=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/user/enable/".concat(t),{method:"PUT"}));case 1:case"end":return e.stop()}},n)})),M.apply(this,arguments)}function De(n){return C.apply(this,arguments)}function C(){return C=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/user/disable/".concat(t),{method:"PUT"}));case 1:case"end":return e.stop()}},n)})),C.apply(this,arguments)}function Re(n,t,a){return A.apply(this,arguments)}function A(){return A=s()(u()().mark(function n(t,a,e){return u()().wrap(function(i){for(;;)switch(i.prev=i.next){case 0:return i.abrupt("return",(0,o.request)("/api/user/notin_group_list",{params:m()(m()(m()({},t),(0,f.YE)(a)),(0,f.oH)(e))}).then(function(r){var c,p,d,_,h={data:r!=null&&(c=r.result)!==null&&c!==void 0&&c.list?r==null||(p=r.result)===null||p===void 0?void 0:p.list:[],success:r==null?void 0:r.success,total:r!=null&&(d=r.result)!==null&&d!==void 0&&d.pagination?r==null||(_=r.result)===null||_===void 0?void 0:_.pagination.total:0};return Promise.resolve(h)}));case 1:case"end":return i.stop()}},n)})),A.apply(this,arguments)}function Me(n,t,a){return B.apply(this,arguments)}function B(){return B=s()(u()().mark(function n(t,a,e){return u()().wrap(function(i){for(;;)switch(i.prev=i.next){case 0:return i.abrupt("return",(0,o.request)("/api/user_group/list",{params:m()(m()(m()({},t),(0,f.YE)(a)),(0,f.oH)(e))}).then(function(r){var c,p,d,_,h={data:r!=null&&(c=r.result)!==null&&c!==void 0&&c.list?r==null||(p=r.result)===null||p===void 0?void 0:p.list:[],success:r==null?void 0:r.success,total:r!=null&&(d=r.result)!==null&&d!==void 0&&d.pagination?r==null||(_=r.result)===null||_===void 0?void 0:_.pagination.total:0};return Promise.resolve(h)}));case 1:case"end":return i.stop()}},n)})),B.apply(this,arguments)}function Ce(n){return K.apply(this,arguments)}function K(){return K=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/user_group/create",{method:"POST",requestType:"json",data:t}));case 1:case"end":return e.stop()}},n)})),K.apply(this,arguments)}function Ae(n){return H.apply(this,arguments)}function H(){return H=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/user_group/get/".concat(t),{method:"GET"}));case 1:case"end":return e.stop()}},n)})),H.apply(this,arguments)}function Be(n){return W.apply(this,arguments)}function W(){return W=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/user_group/update",{method:"PUT",requestType:"json",data:t}));case 1:case"end":return e.stop()}},n)})),W.apply(this,arguments)}function Ke(n){return Y.apply(this,arguments)}function Y(){return Y=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/user_group/delete/".concat(t),{method:"DELETE"}));case 1:case"end":return e.stop()}},n)})),Y.apply(this,arguments)}function He(n,t){return N.apply(this,arguments)}function N(){return N=s()(u()().mark(function n(t,a){return u()().wrap(function(l){for(;;)switch(l.prev=l.next){case 0:return l.abrupt("return",(0,o.request)("/api/user_group/remove_member/".concat(t),{params:{userId:a},method:"DELETE"}));case 1:case"end":return l.stop()}},n)})),N.apply(this,arguments)}function We(n,t){return V.apply(this,arguments)}function V(){return V=s()(u()().mark(function n(t,a){return u()().wrap(function(l){for(;;)switch(l.prev=l.next){case 0:return l.abrupt("return",(0,o.request)("/api/user_group/batch/remove_member/".concat(t),{method:"DELETE",params:{userIds:a},paramsSerializer:function(r){return pe().stringify(r,{indices:!1})}}));case 1:case"end":return l.stop()}},n)})),V.apply(this,arguments)}function Ye(n,t,a){return F.apply(this,arguments)}function F(){return F=s()(u()().mark(function n(t,a,e){return u()().wrap(function(i){for(;;)switch(i.prev=i.next){case 0:return i.abrupt("return",(0,o.request)("/api/user_group/".concat(t.id,"/member_list"),{params:m()(m()(m()({},t),(0,f.YE)(a)),(0,f.oH)(e))}).then(function(r){var c,p,d,_,h={data:r!=null&&(c=r.result)!==null&&c!==void 0&&c.list?r==null||(p=r.result)===null||p===void 0?void 0:p.list:[],success:r==null?void 0:r.success,total:r!=null&&(d=r.result)!==null&&d!==void 0&&d.pagination?r==null||(_=r.result)===null||_===void 0?void 0:_.pagination.total:0};return Promise.resolve(h)}));case 1:case"end":return i.stop()}},n)})),F.apply(this,arguments)}function Ne(n,t){return X.apply(this,arguments)}function X(){return X=s()(u()().mark(function n(t,a){return u()().wrap(function(l){for(;;)switch(l.prev=l.next){case 0:return l.abrupt("return",(0,o.request)("/api/user_group/add_member/".concat(t),{method:"POST",requestType:"form",params:{userIds:a},paramsSerializer:function(r){return pe().stringify(r,{indices:!1})}}));case 1:case"end":return l.stop()}},n)})),X.apply(this,arguments)}function Ve(n,t,a){return Q.apply(this,arguments)}function Q(){return Q=s()(u()().mark(function n(t,a,e){return u()().wrap(function(i){for(;;)switch(i.prev=i.next){case 0:return i.abrupt("return",(0,o.request)("/api/identity_source/list",{params:m()(m()(m()({},t),(0,f.YE)(a)),(0,f.oH)(e))}).then(function(r){var c,p,d,_,h={data:r!=null&&(c=r.result)!==null&&c!==void 0&&c.list?r==null||(p=r.result)===null||p===void 0?void 0:p.list:[],success:r==null?void 0:r.success,total:r!=null&&(d=r.result)!==null&&d!==void 0&&d.pagination?r==null||(_=r.result)===null||_===void 0?void 0:_.pagination.total:0};return Promise.resolve(h)}));case 1:case"end":return i.stop()}},n)})),Q.apply(this,arguments)}function Fe(n){return Z.apply(this,arguments)}function Z(){return Z=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/identity_source/get/".concat(t)));case 1:case"end":return e.stop()}},n)})),Z.apply(this,arguments)}function Xe(n){return J.apply(this,arguments)}function J(){return J=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/identity_source/get/config/".concat(t)));case 1:case"end":return e.stop()}},n)})),J.apply(this,arguments)}function Qe(n){return x.apply(this,arguments)}function x(){return x=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/identity_source/sync/execute/".concat(t),{method:"POST"}));case 1:case"end":return e.stop()}},n)})),x.apply(this,arguments)}function Ze(n){return ee.apply(this,arguments)}function ee(){return ee=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/identity_source/create",{data:t,method:"POST",requestType:"json"}));case 1:case"end":return e.stop()}},n)})),ee.apply(this,arguments)}function Je(n){return re.apply(this,arguments)}function re(){return re=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/identity_source/update",{data:t,method:"PUT",requestType:"json"}));case 1:case"end":return e.stop()}},n)})),re.apply(this,arguments)}function xe(n){return ne.apply(this,arguments)}function ne(){return ne=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/identity_source/save/config",{method:"PUT",data:t,requestType:"json"}));case 1:case"end":return e.stop()}},n)})),ne.apply(this,arguments)}function er(n){return te.apply(this,arguments)}function te(){return te=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/identity_source/enable/".concat(t),{method:"PUT"}));case 1:case"end":return e.stop()}},n)})),te.apply(this,arguments)}function rr(n){return ue.apply(this,arguments)}function ue(){return ue=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/identity_source/disable/".concat(t),{method:"PUT"}));case 1:case"end":return e.stop()}},n)})),ue.apply(this,arguments)}function nr(n){return ae.apply(this,arguments)}function ae(){return ae=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/identity_source/delete/".concat(t),{method:"DELETE"}));case 1:case"end":return e.stop()}},n)})),ae.apply(this,arguments)}function tr(n){return ie.apply(this,arguments)}function ie(){return ie=s()(u()().mark(function n(t){return u()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",(0,o.request)("/api/identity_source/config_validator",{method:"POST",data:t,requestType:"json"}));case 1:case"end":return e.stop()}},n)})),ie.apply(this,arguments)}function ur(n,t,a){return se.apply(this,arguments)}function se(){return se=s()(u()().mark(function n(t,a,e){return u()().wrap(function(i){for(;;)switch(i.prev=i.next){case 0:return i.abrupt("return",(0,o.request)("/api/identity_source/sync/history_list",{params:m()(m()(m()({},t),(0,f.YE)(a)),(0,f.oH)(e))}).then(function(r){var c,p,d,_,h={data:r!=null&&(c=r.result)!==null&&c!==void 0&&c.list?r==null||(p=r.result)===null||p===void 0?void 0:p.list:[],success:r==null?void 0:r.success,total:r!=null&&(d=r.result)!==null&&d!==void 0&&d.pagination?r==null||(_=r.result)===null||_===void 0?void 0:_.pagination.total:0};return Promise.resolve(h)}));case 1:case"end":return i.stop()}},n)})),se.apply(this,arguments)}function ar(n,t,a){return oe.apply(this,arguments)}function oe(){return oe=s()(u()().mark(function n(t,a,e){return u()().wrap(function(i){for(;;)switch(i.prev=i.next){case 0:return i.abrupt("return",(0,o.request)("/api/identity_source/sync/record_list",{params:m()(m()(m()({},t),(0,f.YE)(a)),(0,f.oH)(e))}).then(function(r){var c,p,d,_,h={data:r!=null&&(c=r.result)!==null&&c!==void 0&&c.list?r==null||(p=r.result)===null||p===void 0?void 0:p.list:[],success:r==null?void 0:r.success,total:r!=null&&(d=r.result)!==null&&d!==void 0&&d.pagination?r==null||(_=r.result)===null||_===void 0?void 0:_.pagination.total:0};return Promise.resolve(h)}));case 1:case"end":return i.stop()}},n)})),oe.apply(this,arguments)}function ir(n,t,a){return le.apply(this,arguments)}function le(){return le=s()(u()().mark(function n(t,a,e){return u()().wrap(function(i){for(;;)switch(i.prev=i.next){case 0:return i.abrupt("return",(0,o.request)("/api/identity_source/event/record_list",{params:m()(m()(m()({},t),(0,f.YE)(a)),(0,f.oH)(e))}).then(function(r){var c,p,d,_,h={data:r!=null&&(c=r.result)!==null&&c!==void 0&&c.list?r==null||(p=r.result)===null||p===void 0?void 0:p.list:[],success:r==null?void 0:r.success,total:r!=null&&(d=r.result)!==null&&d!==void 0&&d.pagination?r==null||(_=r.result)===null||_===void 0?void 0:_.pagination.total:0};return Promise.resolve(h)}));case 1:case"end":return i.stop()}},n)})),le.apply(this,arguments)}function sr(){return ce.apply(this,arguments)}function ce(){return ce=s()(u()().mark(function n(){return u()().wrap(function(a){for(;;)switch(a.prev=a.next){case 0:return a.abrupt("return",(0,o.request)("/api/password/generate",{method:"GET"}));case 1:case"end":return a.stop()}},n)})),ce.apply(this,arguments)}}}]); diff --git a/eiam-console/src/main/resources/fe/1576.0ddabdb9.async.js b/eiam-console/src/main/resources/fe/1576.0ddabdb9.async.js new file mode 100644 index 00000000..6e023597 --- /dev/null +++ b/eiam-console/src/main/resources/fe/1576.0ddabdb9.async.js @@ -0,0 +1,18 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +"use strict";(self.webpackChunktopiam_console=self.webpackChunktopiam_console||[]).push([[1576],{41576:function(e,n,t){t.r(n),t.d(n,{default:function(){return s}});var c=t(67294),u=t(98971),o=t(85893);function s(){var E=(0,u.useOutletContext)();return(0,o.jsx)(u.Outlet,{context:E})}}}]); diff --git a/eiam-console/src/main/resources/fe/1687.d91fbb33.async.js b/eiam-console/src/main/resources/fe/1687.d91fbb33.async.js new file mode 100644 index 00000000..2d85a423 --- /dev/null +++ b/eiam-console/src/main/resources/fe/1687.d91fbb33.async.js @@ -0,0 +1,25 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +"use strict";(self.webpackChunktopiam_console=self.webpackChunktopiam_console||[]).push([[1687],{21687:function(De,Y,f){f.d(Y,{Z:function(){return Ae}});var l=f(4942),i=f(87462),b=f(97685),q=f(71002),_=f(94184),S=f.n(_),d=f(67294),H=f(53124),$=f(98423),ee=function(e){var t,a,n=e.prefixCls,s=e.className,c=e.style,o=e.size,u=e.shape,g=S()((t={},(0,l.Z)(t,n+"-lg",o==="large"),(0,l.Z)(t,n+"-sm",o==="small"),t)),m=S()((a={},(0,l.Z)(a,n+"-circle",u==="circle"),(0,l.Z)(a,n+"-square",u==="square"),(0,l.Z)(a,n+"-round",u==="round"),a)),v=d.useMemo(function(){return typeof o=="number"?{width:o,height:o,lineHeight:o+"px"}:{}},[o]);return d.createElement("span",{className:S()(n,g,m,s),style:(0,i.Z)((0,i.Z)({},v),c)})},N=ee,te=f(66981),le=f(67968),ae=f(45503),ne=new te.E4("ant-skeleton-loading",{"0%":{transform:"translateX(-37.5%)"},"100%":{transform:"translateX(37.5%)"}}),P=function(e){return{height:e,lineHeight:e+"px"}},I=function(e){return(0,i.Z)({width:e},P(e))},re=function(e){return{position:"relative",zIndex:0,overflow:"hidden",background:"transparent","&::after":{position:"absolute",top:0,insetInlineEnd:"-150%",bottom:0,insetInlineStart:"-150%",background:e.skeletonLoadingBackground,animationName:ne,animationDuration:e.skeletonLoadingMotionDuration,animationTimingFunction:"ease",animationIterationCount:"infinite",content:'""'}}},T=function(e){return(0,i.Z)({width:e*5,minWidth:e*5},P(e))},ie=function(e){var t,a=e.skeletonAvatarCls,n=e.color,s=e.controlHeight,c=e.controlHeightLG,o=e.controlHeightSM;return t={},(0,l.Z)(t,""+a,(0,i.Z)({display:"inline-block",verticalAlign:"top",background:n},I(s))),(0,l.Z)(t,""+a+a+"-circle",{borderRadius:"50%"}),(0,l.Z)(t,""+a+a+"-lg",(0,i.Z)({},I(c))),(0,l.Z)(t,""+a+a+"-sm",(0,i.Z)({},I(o))),t},oe=function(e){var t,a=e.controlHeight,n=e.borderRadiusSM,s=e.skeletonInputCls,c=e.controlHeightLG,o=e.controlHeightSM,u=e.color;return t={},(0,l.Z)(t,""+s,(0,i.Z)({display:"inline-block",verticalAlign:"top",background:u,borderRadius:n},T(a))),(0,l.Z)(t,s+"-lg",(0,i.Z)({},T(c))),(0,l.Z)(t,s+"-sm",(0,i.Z)({},T(o))),t},F=function(e){return(0,i.Z)({width:e},P(e))},se=function(e){var t,a,n=e.skeletonImageCls,s=e.imageSizeBase,c=e.color,o=e.borderRadiusSM;return a={},(0,l.Z)(a,""+n,(0,i.Z)((0,i.Z)({display:"flex",alignItems:"center",justifyContent:"center",verticalAlign:"top",background:c,borderRadius:o},F(s*2)),(t={},(0,l.Z)(t,n+"-path",{fill:"#bfbfbf"}),(0,l.Z)(t,n+"-svg",(0,i.Z)((0,i.Z)({},F(s)),{maxWidth:s*4,maxHeight:s*4})),(0,l.Z)(t,n+"-svg"+n+"-svg-circle",{borderRadius:"50%"}),t))),(0,l.Z)(a,""+n+n+"-circle",{borderRadius:"50%"}),a},G=function(e,t,a){var n,s=e.skeletonButtonCls;return n={},(0,l.Z)(n,""+a+s+"-circle",{width:t,minWidth:t,borderRadius:"50%"}),(0,l.Z)(n,""+a+s+"-round",{borderRadius:t}),n},D=function(e){return(0,i.Z)({width:e*2,minWidth:e*2},P(e))},ce=function(e){var t=e.borderRadiusSM,a=e.skeletonButtonCls,n=e.controlHeight,s=e.controlHeightLG,c=e.controlHeightSM,o=e.color;return(0,i.Z)((0,i.Z)((0,i.Z)((0,i.Z)((0,i.Z)((0,l.Z)({},""+a,(0,i.Z)({display:"inline-block",verticalAlign:"top",background:o,borderRadius:t,width:n*2,minWidth:n*2},D(n))),G(e,n,a)),(0,l.Z)({},a+"-lg",(0,i.Z)({},D(s)))),G(e,s,a+"-lg")),(0,l.Z)({},a+"-sm",(0,i.Z)({},D(c)))),G(e,c,a+"-sm"))},ue=function(e){var t,a,n,s,c,o=e.componentCls,u=e.skeletonAvatarCls,g=e.skeletonTitleCls,m=e.skeletonParagraphCls,v=e.skeletonButtonCls,h=e.skeletonInputCls,Z=e.skeletonImageCls,x=e.controlHeight,k=e.controlHeightLG,E=e.controlHeightSM,y=e.color,C=e.padding,W=e.marginSM,M=e.borderRadius,X=e.skeletonTitleHeight,A=e.skeletonBlockRadius,p=e.skeletonParagraphLineHeight,R=e.controlHeightXS,B=e.skeletonParagraphMarginTop;return c={},(0,l.Z)(c,""+o,(n={display:"table",width:"100%"},(0,l.Z)(n,o+"-header",(t={display:"table-cell",paddingInlineEnd:C,verticalAlign:"top"},(0,l.Z)(t,""+u,(0,i.Z)({display:"inline-block",verticalAlign:"top",background:y},I(x))),(0,l.Z)(t,u+"-circle",{borderRadius:"50%"}),(0,l.Z)(t,u+"-lg",(0,i.Z)({},I(k))),(0,l.Z)(t,u+"-sm",(0,i.Z)({},I(E))),t)),(0,l.Z)(n,o+"-content",(a={display:"table-cell",width:"100%",verticalAlign:"top"},(0,l.Z)(a,""+g,(0,l.Z)({width:"100%",height:X,background:y,borderRadius:A},"+ "+m,{marginBlockStart:E})),(0,l.Z)(a,""+m,{padding:0,"> li":{width:"100%",height:p,listStyle:"none",background:y,borderRadius:A,"+ li":{marginBlockStart:R}}}),(0,l.Z)(a,m+"> li:last-child:not(:first-child):not(:nth-child(2))",{width:"61%"}),a)),(0,l.Z)(n,"&-round "+o+"-content",(0,l.Z)({},g+", "+m+" > li",{borderRadius:M})),n)),(0,l.Z)(c,o+"-with-avatar "+o+"-content",(0,l.Z)({},""+g,(0,l.Z)({marginBlockStart:W},"+ "+m,{marginBlockStart:B}))),(0,l.Z)(c,""+o+o+"-element",(0,i.Z)((0,i.Z)((0,i.Z)((0,i.Z)({display:"inline-block",width:"auto"},ce(e)),ie(e)),oe(e)),se(e))),(0,l.Z)(c,""+o+o+"-block",(s={width:"100%"},(0,l.Z)(s,""+v,{width:"100%"}),(0,l.Z)(s,""+h,{width:"100%"}),s)),(0,l.Z)(c,""+o+o+"-active",(0,l.Z)({},` + `+g+`, + `+m+` > li, + `+u+`, + `+v+`, + `+h+`, + `+Z+` + `,(0,i.Z)({},re(e)))),c},w=(0,le.Z)("Skeleton",function(r){var e=r.componentCls,t=(0,ae.TS)(r,{skeletonAvatarCls:e+"-avatar",skeletonTitleCls:e+"-title",skeletonParagraphCls:e+"-paragraph",skeletonButtonCls:e+"-button",skeletonInputCls:e+"-input",skeletonImageCls:e+"-image",imageSizeBase:r.controlHeight*1.5,skeletonTitleHeight:r.controlHeight/2,skeletonBlockRadius:r.borderRadiusSM,skeletonParagraphLineHeight:r.controlHeight/2,skeletonParagraphMarginTop:r.marginLG+r.marginXXS,borderRadius:100,skeletonLoadingBackground:"linear-gradient(90deg, "+r.color+" 25%, "+r.colorGradientEnd+" 37%, "+r.color+" 63%)",skeletonLoadingMotionDuration:"1.4s"});return[ue(t)]},function(r){var e=r.colorFillContent,t=r.colorFill;return{color:e,colorGradientEnd:t}}),de=function(e){var t=e.prefixCls,a=e.className,n=e.active,s=e.shape,c=s===void 0?"circle":s,o=e.size,u=o===void 0?"default":o,g=d.useContext(H.E_),m=g.getPrefixCls,v=m("skeleton",t),h=w(v),Z=(0,b.Z)(h,2),x=Z[0],k=Z[1],E=(0,$.Z)(e,["prefixCls","className"]),y=S()(v,v+"-element",(0,l.Z)({},v+"-active",n),a,k);return x(d.createElement("div",{className:y},d.createElement(N,(0,i.Z)({prefixCls:v+"-avatar",shape:c,size:u},E))))},ve=de,ge=function(e){var t,a=e.prefixCls,n=e.className,s=e.active,c=e.block,o=c===void 0?!1:c,u=e.size,g=u===void 0?"default":u,m=d.useContext(H.E_),v=m.getPrefixCls,h=v("skeleton",a),Z=w(h),x=(0,b.Z)(Z,2),k=x[0],E=x[1],y=(0,$.Z)(e,["prefixCls"]),C=S()(h,h+"-element",(t={},(0,l.Z)(t,h+"-active",s),(0,l.Z)(t,h+"-block",o),t),n,E);return k(d.createElement("div",{className:C},d.createElement(N,(0,i.Z)({prefixCls:h+"-button",size:g},y))))},me=ge,j=f(1413),he={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M888 792H200V168c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v688c0 4.4 3.6 8 8 8h752c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM288 604a64 64 0 10128 0 64 64 0 10-128 0zm118-224a48 48 0 1096 0 48 48 0 10-96 0zm158 228a96 96 0 10192 0 96 96 0 10-192 0zm148-314a56 56 0 10112 0 56 56 0 10-112 0z"}}]},name:"dot-chart",theme:"outlined"},fe=he,Ze=f(84089),V=function(e,t){return d.createElement(Ze.Z,(0,j.Z)((0,j.Z)({},e),{},{ref:t,icon:fe}))};V.displayName="DotChartOutlined";var Ce=d.forwardRef(V),Se=function(e){var t=e.prefixCls,a=e.className,n=e.style,s=e.active,c=e.children,o=d.useContext(H.E_),u=o.getPrefixCls,g=u("skeleton",t),m=w(g),v=(0,b.Z)(m,2),h=v[0],Z=v[1],x=S()(g,g+"-element",(0,l.Z)({},g+"-active",s),Z,a),k=c!=null?c:d.createElement(Ce,null);return h(d.createElement("div",{className:x},d.createElement("div",{className:S()(g+"-image",a),style:n},k)))},xe=Se,ke="M365.714286 329.142857q0 45.714286-32.036571 77.677714t-77.677714 32.036571-77.677714-32.036571-32.036571-77.677714 32.036571-77.677714 77.677714-32.036571 77.677714 32.036571 32.036571 77.677714zM950.857143 548.571429l0 256-804.571429 0 0-109.714286 182.857143-182.857143 91.428571 91.428571 292.571429-292.571429zM1005.714286 146.285714l-914.285714 0q-7.460571 0-12.873143 5.412571t-5.412571 12.873143l0 694.857143q0 7.460571 5.412571 12.873143t12.873143 5.412571l914.285714 0q7.460571 0 12.873143-5.412571t5.412571-12.873143l0-694.857143q0-7.460571-5.412571-12.873143t-12.873143-5.412571zM1097.142857 164.571429l0 694.857143q0 37.741714-26.843429 64.585143t-64.585143 26.843429l-914.285714 0q-37.741714 0-64.585143-26.843429t-26.843429-64.585143l0-694.857143q0-37.741714 26.843429-64.585143t64.585143-26.843429l914.285714 0q37.741714 0 64.585143 26.843429t26.843429 64.585143z",ye=function(e){var t=e.prefixCls,a=e.className,n=e.style,s=e.active,c=d.useContext(H.E_),o=c.getPrefixCls,u=o("skeleton",t),g=w(u),m=(0,b.Z)(g,2),v=m[0],h=m[1],Z=S()(u,u+"-element",(0,l.Z)({},u+"-active",s),a,h);return v(d.createElement("div",{className:Z},d.createElement("div",{className:S()(u+"-image",a),style:n},d.createElement("svg",{viewBox:"0 0 1098 1024",xmlns:"http://www.w3.org/2000/svg",className:u+"-image-svg"},d.createElement("path",{d:ke,className:u+"-image-path"})))))},Ee=ye,pe=function(e){var t,a=e.prefixCls,n=e.className,s=e.active,c=e.block,o=e.size,u=o===void 0?"default":o,g=d.useContext(H.E_),m=g.getPrefixCls,v=m("skeleton",a),h=w(v),Z=(0,b.Z)(h,2),x=Z[0],k=Z[1],E=(0,$.Z)(e,["prefixCls"]),y=S()(v,v+"-element",(t={},(0,l.Z)(t,v+"-active",s),(0,l.Z)(t,v+"-block",c),t),n,k);return x(d.createElement("div",{className:y},d.createElement(N,(0,i.Z)({prefixCls:v+"-input",size:u},E))))},be=pe,He=f(74902),Ie=function(e){var t=function(g){var m=e.width,v=e.rows,h=v===void 0?2:v;if(Array.isArray(m))return m[g];if(h-1===g)return m},a=e.prefixCls,n=e.className,s=e.style,c=e.rows,o=(0,He.Z)(Array(c)).map(function(u,g){return d.createElement("li",{key:g,style:{width:t(g)}})});return d.createElement("ul",{className:S()(a,n),style:s},o)},we=Ie,ze=function(e){var t=e.prefixCls,a=e.className,n=e.width,s=e.style;return d.createElement("h3",{className:S()(t,a),style:(0,i.Z)({width:n},s)})},Re=ze;function O(r){return r&&(0,q.Z)(r)==="object"?r:{}}function Be(r,e){return r&&!e?{size:"large",shape:"square"}:{size:"large",shape:"circle"}}function Ne(r,e){return!r&&e?{width:"38%"}:r&&e?{width:"50%"}:{}}function Pe(r,e){var t={};return(!r||!e)&&(t.width="61%"),!r&&e?t.rows=3:t.rows=2,t}var z=function(e){var t=e.prefixCls,a=e.loading,n=e.className,s=e.style,c=e.children,o=e.avatar,u=o===void 0?!1:o,g=e.title,m=g===void 0?!0:g,v=e.paragraph,h=v===void 0?!0:v,Z=e.active,x=e.round,k=d.useContext(H.E_),E=k.getPrefixCls,y=k.direction,C=E("skeleton",t),W=w(C),M=(0,b.Z)(W,2),X=M[0],A=M[1];if(a||!("loading"in e)){var p,R=!!u,B=!!m,L=!!h,J;if(R){var Le=(0,i.Z)((0,i.Z)({prefixCls:C+"-avatar"},Be(B,L)),O(u));J=d.createElement("div",{className:C+"-header"},d.createElement(N,(0,i.Z)({},Le)))}var K;if(B||L){var Q;if(B){var $e=(0,i.Z)((0,i.Z)({prefixCls:C+"-title"},Ne(R,L)),O(m));Q=d.createElement(Re,(0,i.Z)({},$e))}var U;if(L){var Te=(0,i.Z)((0,i.Z)({prefixCls:C+"-paragraph"},Pe(R,B)),O(h));U=d.createElement(we,(0,i.Z)({},Te))}K=d.createElement("div",{className:C+"-content"},Q,U)}var Ge=S()(C,(p={},(0,l.Z)(p,C+"-with-avatar",R),(0,l.Z)(p,C+"-active",Z),(0,l.Z)(p,C+"-rtl",y==="rtl"),(0,l.Z)(p,C+"-round",x),p),n,A);return X(d.createElement("div",{className:Ge,style:s},J,K))}return typeof c!="undefined"?c:null};z.Button=me,z.Avatar=ve,z.Input=be,z.Image=Ee,z.Node=xe;var Me=z,Ae=Me}}]); diff --git a/eiam-console/src/main/resources/fe/1914.479e05f1.async.js b/eiam-console/src/main/resources/fe/1914.479e05f1.async.js new file mode 100644 index 00000000..fa28c019 --- /dev/null +++ b/eiam-console/src/main/resources/fe/1914.479e05f1.async.js @@ -0,0 +1,126 @@ +/* + * eiam-console - Employee Identity and Access Management Program + * Copyright © 2020-2022 TopIAM (support@topiam.cn) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +(self.webpackChunktopiam_console=self.webpackChunktopiam_console||[]).push([[1914],{39398:function(Cn,ve,e){"use strict";e.d(ve,{Z:function(){return t}});var r=e(1413),a=e(67294),F={icon:{tag:"svg",attrs:{viewBox:"0 0 1024 1024",focusable:"false"},children:[{tag:"path",attrs:{d:"M840.4 300H183.6c-19.7 0-30.7 20.8-18.5 35l328.4 380.8c9.4 10.9 27.5 10.9 37 0L858.9 335c12.2-14.2 1.2-35-18.5-35z"}}]},name:"caret-down",theme:"outlined"},C=F,N=e(84089),$=function(T,h){return a.createElement(N.Z,(0,r.Z)((0,r.Z)({},T),{},{ref:h,icon:C}))};$.displayName="CaretDownOutlined";var t=a.forwardRef($)},86548:function(Cn,ve,e){"use strict";e.d(ve,{Z:function(){return t}});var r=e(1413),a=e(67294),F={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z"}}]},name:"edit",theme:"outlined"},C=F,N=e(84089),$=function(T,h){return a.createElement(N.Z,(0,r.Z)((0,r.Z)({},T),{},{ref:h,icon:C}))};$.displayName="EditOutlined";var t=a.forwardRef($)},66017:function(Cn,ve,e){"use strict";e.d(ve,{Z:function(){return t}});var r=e(1413),a=e(67294),F={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M859.9 780H164.1c-4.5 0-8.1 3.6-8.1 8v60c0 4.4 3.6 8 8.1 8h695.8c4.5 0 8.1-3.6 8.1-8v-60c0-4.4-3.6-8-8.1-8zM505.7 669a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V176c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8z"}}]},name:"vertical-align-bottom",theme:"outlined"},C=F,N=e(84089),$=function(T,h){return a.createElement(N.Z,(0,r.Z)((0,r.Z)({},T),{},{ref:h,icon:C}))};$.displayName="VerticalAlignBottomOutlined";var t=a.forwardRef($)},84517:function(Cn,ve,e){"use strict";var r=e(38345);ve.ZP=r.Z},59022:function(Cn,ve,e){"use strict";e.d(ve,{Z:function(){return X}});var r=e(1413),a=e(97685),F=e(91),C=e(85893),N=e(51280),$=e(22270),t=e(81736),j=e(51812),T=e(43825),h=e(97435),Z=e(67294),pe=e(71975),L=e(4942),ye={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M880.1 154H143.9c-24.5 0-39.8 26.7-27.5 48L349 597.4V838c0 17.7 14.2 32 31.8 32h262.4c17.6 0 31.8-14.3 31.8-32V597.4L907.7 202c12.2-21.3-3.1-48-27.6-48zM603.4 798H420.6V642h182.9v156zm9.6-236.6l-9.5 16.6h-183l-9.5-16.6L212.7 226h598.6L613 561.4z"}}]},name:"filter",theme:"outlined"},Ce=ye,je=e(84089),B=function(l,G){return Z.createElement(je.Z,(0,r.Z)((0,r.Z)({},l),{},{ref:G,icon:Ce}))};B.displayName="FilterOutlined";var H=Z.forwardRef(B),Se=e(73964),we=e(2122),ee=e(1336),M=e(46986),Ie=e(94184),ue=e.n(Ie),Ee=e(89671),_e=e(14855),xt=function(l){return(0,L.Z)({},l.componentCls,{lineHeight:"30px","&::before":{display:"block",height:0,visibility:"hidden",content:"'.'"},"&-small":{lineHeight:l.lineHeight},"&-container":{display:"flex",flexWrap:"wrap",gap:8},"&-item":(0,L.Z)({whiteSpace:"nowrap"},"".concat(l.antCls,"-form-item"),{marginBlock:0}),"&-line":{minWidth:"198px"},"&-line:not(:first-child)":{marginBlockStart:"16px",marginBlockEnd:8},"&-collapse-icon":{width:l.controlHeight,height:l.controlHeight,borderRadius:"50%",display:"flex",alignItems:"center",justifyContent:"center"},"&-effective":(0,L.Z)({},"".concat(l.componentCls,"-collapse-icon"),{backgroundColor:l.colorBgTextHover})})};function be(w){return(0,_e.Xj)("LightFilter",function(l){var G=(0,r.Z)((0,r.Z)({},l),{},{componentCls:".".concat(w)});return[xt(G)]})}var Nn=["size","collapse","collapseLabel","initialValues","onValuesChange","form","placement","formRef","bordered","ignoreRules","footerRender"],ge=function(l){var G=l.items,ne=l.prefixCls,q=l.size,Y=q===void 0?"middle":q,ot=l.collapse,at=l.collapseLabel,ct=l.onValuesChange,Qe=l.bordered,Le=l.values,Ue=l.footerRender,ut=l.placement,ht=(0,Se.YB)(),Pt="".concat(ne,"-light-filter"),yt=be(Pt),en=yt.wrapSSR,Bt=yt.hashId,an=(0,Z.useState)(!1),Nt=(0,a.Z)(an,2),Xt=Nt[0],on=Nt[1],it=(0,Z.useState)(function(){return(0,r.Z)({},Le)}),Ne=(0,a.Z)(it,2),et=Ne[0],un=Ne[1];(0,Z.useEffect)(function(){un((0,r.Z)({},Le))},[Le]);var Tn=(0,Z.useMemo)(function(){var Mn=[],Me=[];return G.forEach(function(bt){var Zn=bt.props||{},tn=Zn.secondary;tn||ot?Mn.push(bt):Me.push(bt)}),{collapseItems:Mn,outsideItems:Me}},[l.items]),An=Tn.collapseItems,ir=Tn.outsideItems,Yn=function(){return at||(ot?(0,C.jsx)(H,{className:"".concat(Pt,"-collapse-icon ").concat(Bt)}):(0,C.jsx)(we.Q,{size:Y,label:ht.getMessage("form.lightFilter.more","\u66F4\u591A\u7B5B\u9009"),expanded:Xt}))};return en((0,C.jsx)("div",{className:ue()(Pt,Bt,"".concat(Pt,"-").concat(Y),(0,L.Z)({},"".concat(Pt,"-effective"),Object.keys(Le).some(function(Mn){return Le[Mn]}))),children:(0,C.jsxs)("div",{className:"".concat(Pt,"-container ").concat(Bt),children:[ir.map(function(Mn,Me){var bt=Mn.key,Zn=Mn.props.fieldProps,tn=Zn!=null&&Zn.placement?Zn==null?void 0:Zn.placement:ut;return(0,C.jsx)("div",{className:"".concat(Pt,"-item ").concat(Bt),children:Z.cloneElement(Mn,{fieldProps:(0,r.Z)((0,r.Z)({},Mn.props.fieldProps),{},{placement:tn}),proFieldProps:{light:!0,label:Mn.props.label,bordered:Qe},bordered:Qe})},bt||Me)}),An.length?(0,C.jsx)("div",{className:"".concat(Pt,"-item ").concat(Bt),children:(0,C.jsx)(ee.M,{padding:24,open:Xt,onOpenChange:on,placement:ut,label:Yn(),footerRender:Ue,footer:{onConfirm:function(){ct((0,r.Z)({},et)),on(!1)},onClear:function(){var Me={};An.forEach(function(bt){var Zn=bt.props.name;Me[Zn]=void 0}),ct(Me)}},children:An.map(function(Mn){var Me=Mn.key,bt=Mn.props,Zn=bt.name,tn=bt.fieldProps,Xn=(0,r.Z)((0,r.Z)({},tn),{},{onChange:function(Qn){return un((0,r.Z)((0,r.Z)({},et),{},(0,L.Z)({},Zn,Qn!=null&&Qn.target?Qn.target.value:Qn))),!1}});et.hasOwnProperty(Zn)&&(Xn[Mn.props.valuePropName||"value"]=et[Zn]);var In=tn!=null&&tn.placement?tn==null?void 0:tn.placement:ut;return(0,C.jsx)("div",{className:"".concat(Pt,"-line ").concat(Bt),children:Z.cloneElement(Mn,{fieldProps:(0,r.Z)((0,r.Z)({},Xn),{},{placement:In})})},Me)})})},"more"):null]})}))};function me(w){var l=w.size,G=w.collapse,ne=w.collapseLabel,q=w.initialValues,Y=w.onValuesChange,ot=w.form,at=w.placement,ct=w.formRef,Qe=w.bordered,Le=w.ignoreRules,Ue=w.footerRender,ut=(0,F.Z)(w,Nn),ht=(0,Z.useContext)(M.ZP.ConfigContext),Pt=ht.getPrefixCls,yt=Pt("pro-form"),en=(0,Z.useState)(function(){return(0,r.Z)({},q)}),Bt=(0,a.Z)(en,2),an=Bt[0],Nt=Bt[1],Xt=(0,Z.useRef)();return(0,Z.useImperativeHandle)(ct,function(){return Xt.current}),(0,C.jsx)(Ee.I,(0,r.Z)((0,r.Z)({size:l,initialValues:q,form:ot,contentRender:function(it){return(0,C.jsx)(ge,{prefixCls:yt,items:it.flatMap(function(Ne){return(Ne==null?void 0:Ne.type.displayName)==="ProForm-Group"?Ne.props.children:Ne}),size:l,bordered:Qe,collapse:G,collapseLabel:ne,placement:at,values:an||{},footerRender:Ue,onValuesChange:function(et){var un,Tn,An=(0,r.Z)((0,r.Z)({},an),et);Nt(An),(un=Xt.current)===null||un===void 0||un.setFieldsValue(An),(Tn=Xt.current)===null||Tn===void 0||Tn.submit(),Y&&Y(et,An)}})},formRef:Xt,formItemProps:{colon:!1,labelAlign:"left"},fieldProps:{style:{width:void 0}}},(0,h.Z)(ut,["labelWidth"])),{},{onValuesChange:function(it,Ne){var et;Nt(Ne),Y==null||Y(it,Ne),(et=Xt.current)===null||et===void 0||et.submit()}}))}var Oe=e(37476),dt=e(97269),Te=e(12044),ke=e(60869),Kt=e(15746),Pn=e(71230),qe=e(48555),A=e(80882),pt=e(28700),Zt=e(26713),Lt=function(l,G,ne,q){return l?(0,C.jsxs)(C.Fragment,{children:[ne.getMessage("tableForm.collapsed","\u5C55\u5F00"),q&&"(".concat(q,")"),(0,C.jsx)(A.Z,{style:{marginInlineStart:"0.5em",transition:"0.3s all",transform:"rotate(".concat(l?0:.5,"turn)")}})]}):(0,C.jsxs)(C.Fragment,{children:[ne.getMessage("tableForm.expand","\u6536\u8D77"),(0,C.jsx)(A.Z,{style:{marginInlineStart:"0.5em",transition:"0.3s all",transform:"rotate(".concat(l?0:.5,"turn)")}})]})},Wt=function(l){var G=l.setCollapsed,ne=l.collapsed,q=ne===void 0?!1:ne,Y=l.submitter,ot=l.style,at=l.hiddenNum,ct=(0,Z.useContext)(M.ZP.ConfigContext),Qe=ct.getPrefixCls,Le=(0,Se.YB)(),Ue=(0,Z.useContext)(Se.L_),ut=Ue.hashId,ht=(0,pt.v)(l.collapseRender)||Lt;return(0,C.jsxs)(Zt.Z,{style:ot,size:16,children:[Y,l.collapseRender!==!1&&(0,C.jsx)("a",{className:"".concat(Qe("pro-query-filter-collapse-button")," ").concat(ut),onClick:function(){return G(!q)},children:ht==null?void 0:ht(q,l,Le,at)})]})},$t=Wt,Qt=function(l){var G;return(0,L.Z)({},l.proComponentsCls,(0,L.Z)({},l.componentCls,(G={padding:24},(0,L.Z)(G,"".concat(l.antCls,"-form-item"),{marginBlock:0}),(0,L.Z)(G,"".concat(l.proComponentsCls,"-form-group-title"),{marginBlock:0}),(0,L.Z)(G,"&-row",{rowGap:24,"&-split-line":{"&:after":{position:"absolute",width:"100%",content:'""',height:1,insetBlockEnd:-12,borderBlockEnd:"1px dashed "+l.colorSplit}}}),(0,L.Z)(G,"&-collapse-button",{display:"flex",alignItems:"center"}),G)))};function mn(w){return(0,_e.Xj)("QueryFilter",function(l){var G=(0,r.Z)((0,r.Z)({},l),{},{componentCls:".".concat(w)});return[Qt(G)]})}var le=["collapsed","layout","defaultCollapsed","defaultColsNumber","span","searchGutter","searchText","resetText","optionRender","collapseRender","onReset","onCollapse","labelWidth","style","split","preserve","ignoreRules","showHiddenNum"],We,He,Rt={xs:513,sm:513,md:785,lg:992,xl:1057,xxl:1/0},zt={vertical:[[513,1,"vertical"],[785,2,"vertical"],[1057,3,"vertical"],[1/0,4,"vertical"]],default:[[513,1,"vertical"],[701,2,"vertical"],[1062,3,"horizontal"],[1352,3,"horizontal"],[1/0,4,"horizontal"]]},wt=function(l,G,ne){if(ne&&typeof ne=="number")return{span:ne,layout:l};var q=ne?["xs","sm","md","lg","xl","xxl"].map(function(ot){return[Rt[ot],24/ne[ot],"horizontal"]}):zt[l||"default"],Y=(q||zt.default).find(function(ot){return Gen-1)&&!!Zn&&Xt>=24;on+=1;var R=Z.isValidElement(bt)&&(bt.key||"".concat((zn=bt.props)===null||zn===void 0?void 0:zn.name))||Zn;return Z.isValidElement(bt)&&s?l.preserve?{itemDom:Z.cloneElement(bt,{hidden:!0,key:R||Zn}),hidden:!0,colSpan:qn}:{itemDom:null,colSpan:0,hidden:!0}:{itemDom:bt,colSpan:qn,hidden:!1}}),Tn=un.map(function(bt,Zn){var tn,Xn,In=bt.itemDom,zn=bt.colSpan,Qn=In==null||(tn=In.props)===null||tn===void 0?void 0:tn.hidden;if(Qn)return In;var qn=Z.isValidElement(In)&&(In.key||"".concat((Xn=In.props)===null||Xn===void 0?void 0:Xn.name))||Zn;return 24-et%2417&&Zn(Qn.width)},children:(0,C.jsx)(Ee.I,(0,r.Z)((0,r.Z)({isKeyPressSubmit:!0,preserve:Xt},et),{},{className:ue()(Tn,Yn,et.className),onReset:ht,style:Bt,layout:tn.layout,fieldProps:{style:{width:"100%"}},formItemProps:In,groupProps:{titleStyle:{display:"inline-block",marginInlineEnd:16}},contentRender:function(Qn,qn,m){return(0,C.jsx)(ft,{spanSize:tn,collapsed:l,form:m,collapseRender:ut,defaultCollapsed:q,onCollapse:Pt,optionRender:Ue,submitter:qn,items:Qn,split:an,baseClassName:Tn,resetText:w.resetText,searchText:w.searchText,searchGutter:ct,preserve:Xt,ignoreRules:on,showLength:Xn,showHiddenNum:Ne})}}))},"resize-observer"))}var Be=e(62113),ce=["steps","columns","forceUpdate","grid"],Ge=function(l){var G=l.steps,ne=l.columns,q=l.forceUpdate,Y=l.grid,ot=(0,F.Z)(l,ce),at=(0,N.d)(ot),ct=(0,Z.useCallback)(function(Le){var Ue,ut;(Ue=(ut=at.current).onCurrentChange)===null||Ue===void 0||Ue.call(ut,Le),q([])},[q,at]),Qe=(0,Z.useMemo)(function(){return G==null?void 0:G.map(function(Le,Ue){return(0,Z.createElement)(X,(0,r.Z)((0,r.Z)({grid:Y},Le),{},{key:Ue,layoutType:"StepForm",columns:ne[Ue]}))})},[ne,Y,G]);return(0,C.jsx)(Be.L,(0,r.Z)((0,r.Z)({},ot),{},{onCurrentChange:ct,children:Qe}))},Ye=Ge,Ot=function(l){var G=l.children;return(0,C.jsx)(C.Fragment,{children:G})},z=Ot,V=e(45520),k=e(97462),Ke=function(l,G){var ne=G.genItems;if(l.valueType==="dependency"){var q,Y,ot,at=(q=l.getFieldProps)===null||q===void 0?void 0:q.call(l);return(0,V.noteOnce)(Array.isArray((Y=l.name)!==null&&Y!==void 0?Y:at==null?void 0:at.name),'SchemaForm: fieldProps.name should be NamePath[] when valueType is "dependency"'),(0,V.noteOnce)(typeof l.columns=="function",'SchemaForm: columns should be a function when valueType is "dependency"'),Array.isArray((ot=l.name)!==null&&ot!==void 0?ot:at==null?void 0:at.name)?(0,Z.createElement)(k.Z,(0,r.Z)((0,r.Z)({name:l.name},at),{},{key:l.key}),function(ct){return!l.columns||typeof l.columns!="function"?null:ne(l.columns(ct))}):null}return!0},vt=e(96074),qt=function(l){if(l.valueType==="divider"){var G;return(0,Z.createElement)(vt.Z,(0,r.Z)((0,r.Z)({},(G=l.getFieldProps)===null||G===void 0?void 0:G.call(l)),{},{key:l.key}))}return!0},Sn=e(7772),bn=function(l,G){var ne=G.action,q=G.formRef,Y=G.type,ot=G.originItem,at=(0,r.Z)((0,r.Z)({},(0,h.Z)(l,["dataIndex","width","render","renderFormItem","renderText","title"])),{},{name:l.dataIndex,width:l.width,render:l!=null&&l.render?function(Ue,ut,ht){var Pt,yt,en;return l==null||(Pt=l.render)===null||Pt===void 0?void 0:Pt.call(l,Ue,ut,ht,ne==null?void 0:ne.current,(0,r.Z)((0,r.Z)({type:Y},l),{},{formItemProps:(yt=l.getFormItemProps)===null||yt===void 0?void 0:yt.call(l),fieldProps:(en=l.getFieldProps)===null||en===void 0?void 0:en.call(l)}))}:void 0}),ct=function(){return(0,C.jsx)(Sn.Z,(0,r.Z)((0,r.Z)({},at),{},{ignoreFormItem:!0}))},Qe=l!=null&&l.renderFormItem?function(Ue,ut){var ht,Pt,yt,en=(0,j.Y)((0,r.Z)((0,r.Z)({},ut),{},{onChange:void 0}));return l==null||(ht=l.renderFormItem)===null||ht===void 0?void 0:ht.call(l,(0,r.Z)((0,r.Z)({type:Y},l),{},{formItemProps:(Pt=l.getFormItemProps)===null||Pt===void 0?void 0:Pt.call(l),fieldProps:(yt=l.getFieldProps)===null||yt===void 0?void 0:yt.call(l),originProps:ot}),(0,r.Z)((0,r.Z)({},en),{},{defaultRender:ct,type:Y}),q.current)}:void 0;if(l!=null&&l.renderFormItem){var Le=Qe==null?void 0:Qe(null,{});if(!Le||l.ignoreFormItem)return Le}return(0,Z.createElement)(Sn.Z,(0,r.Z)((0,r.Z)({},at),{},{key:[l.key,l.index||0].join("-"),renderFormItem:Qe}))},Un=e(17186),Dn=function(l,G){var ne=G.genItems;if(l.valueType==="formList"&&l.dataIndex){var q,Y;return!l.columns||!Array.isArray(l.columns)?null:(0,Z.createElement)(Un.u,(0,r.Z)((0,r.Z)({},(q=l.getFormItemProps)===null||q===void 0?void 0:q.call(l)),{},{key:l.key,name:l.dataIndex,label:l.label,initialValue:l.initialValue,colProps:l.colProps,rowProps:l.rowProps},(Y=l.getFieldProps)===null||Y===void 0?void 0:Y.call(l)),ne(l.columns))}return!0},$e=e(74902),Re=e(48171),De=e(69677),Je=e(45598),mt=e(90789),Ut=e(2514),Vt=["children","value","valuePropName","onChange","fieldProps","space","type","transform","convertValue"],Ze=["children","space","valuePropName"],xe={space:Zt.Z,group:De.Z.Group};function he(w){var l=arguments.length<=1?void 0:arguments[1];return l&&l.target&&w in l.target?l.target[w]:l}var oe=function(l){var G=l.children,ne=l.value,q=ne===void 0?[]:ne,Y=l.valuePropName,ot=l.onChange,at=l.fieldProps,ct=l.space,Qe=l.type,Le=Qe===void 0?"space":Qe,Ue=l.transform,ut=l.convertValue,ht=(0,F.Z)(l,Vt),Pt=(0,Re.J)(function(it,Ne){var et,un=(0,$e.Z)(q);un[Ne]=he(Y||"value",it),ot==null||ot(un),at==null||(et=at.onChange)===null||et===void 0||et.call(at,un)}),yt=-1,en=(0,Je.Z)(G).map(function(it){if(Z.isValidElement(it)){var Ne,et,un;yt+=1;var Tn=yt,An=(it==null||(Ne=it.type)===null||Ne===void 0?void 0:Ne.displayName)==="ProFormComponent"||(it==null||(et=it.props)===null||et===void 0?void 0:et.readonly),ir=An?(0,r.Z)((0,r.Z)({key:Tn,ignoreFormItem:!0},it.props||{}),{},{fieldProps:(0,r.Z)((0,r.Z)({},it==null||(un=it.props)===null||un===void 0?void 0:un.fieldProps),{},{onChange:function(){Pt(arguments.length<=0?void 0:arguments[0],Tn)}}),value:q==null?void 0:q[Tn],onChange:void 0}):(0,r.Z)((0,r.Z)({key:Tn},it.props||{}),{},{value:q==null?void 0:q[Tn],onChange:function(Mn){var Me,bt;Pt(Mn,Tn),(Me=(bt=it.props).onChange)===null||Me===void 0||Me.call(bt,Mn)}});return Z.cloneElement(it,ir)}return it}),Bt=xe[Le],an=(0,Ut.zx)(ht),Nt=an.RowWrapper,Xt=(0,Z.useMemo)(function(){return(0,r.Z)({},Le==="group"?{compact:!0}:{})},[Le]),on=(0,Z.useCallback)(function(it){var Ne=it.children;return(0,C.jsx)(Bt,(0,r.Z)((0,r.Z)((0,r.Z)({},Xt),ct),{},{align:"start",children:Ne}))},[Bt,ct,Xt]);return(0,C.jsx)(Nt,{Wrapper:on,children:en})},Dt=Z.forwardRef(function(w,l){var G=w.children,ne=w.space,q=w.valuePropName,Y=(0,F.Z)(w,Ze);return(0,Z.useImperativeHandle)(l,function(){return{}}),(0,C.jsx)(oe,(0,r.Z)((0,r.Z)((0,r.Z)({space:ne,valuePropName:q},Y.fieldProps),{},{onChange:void 0},Y),{},{children:G}))}),Kn=(0,mt.G)(Dt),b=Kn,d=function(l,G){var ne=G.genItems;if(l.valueType==="formSet"&&l.dataIndex){var q,Y;return!l.columns||!Array.isArray(l.columns)?null:(0,Z.createElement)(b,(0,r.Z)((0,r.Z)({},(q=l.getFormItemProps)===null||q===void 0?void 0:q.call(l)),{},{key:l.key,initialValue:l.initialValue,name:l.dataIndex,label:l.label,colProps:l.colProps,rowProps:l.rowProps},(Y=l.getFieldProps)===null||Y===void 0?void 0:Y.call(l)),ne(l.columns))}return!0},o=dt.A.Group,f=function(l,G){var ne=G.genItems;if(l.valueType==="group"){var q;return!l.columns||!Array.isArray(l.columns)?null:(0,C.jsx)(o,(0,r.Z)((0,r.Z)({label:l.label,colProps:l.colProps,rowProps:l.rowProps},(q=l.getFieldProps)===null||q===void 0?void 0:q.call(l)),{},{children:ne(l.columns)}),l.key)}return!0},v=function(l){return l.valueType&&typeof l.valueType=="string"&&["index","indexBorder","option"].includes(l==null?void 0:l.valueType)?null:!0},g=[v,f,Dn,d,qt,Ke],D=function(l,G){for(var ne=0;ne "+v+"-item-container > "+v+"-item-icon",(0,r.Z)({height:"auto",background:"none",border:0},"> "+v+"-icon",{top:g,width:D,height:D,fontSize:P,lineHeight:D+"px"}))),(0,r.Z)(f,"&:not("+v+"-vertical)",(0,r.Z)({},v+"-item-custom",(0,r.Z)({},v+"-item-icon",{width:"auto",background:"none"}))),f},Zt=pt,Lt=function(o){var f,v=o.componentCls,g=o.stepsIconSize,D=o.lineHeight,P=o.stepsSmallIconSize;return(0,r.Z)({},"&"+v+"-label-vertical",(f={},(0,r.Z)(f,v+"-item",{overflow:"visible","&-tail":{marginInlineStart:g/2+o.controlHeightLG,padding:o.paddingXXS+"px "+o.paddingLG+"px"},"&-content":{display:"block",width:(g/2+o.controlHeightLG)*2,marginTop:o.marginSM,textAlign:"center"},"&-icon":{display:"inline-block",marginInlineStart:o.controlHeightLG},"&-title":{paddingInlineEnd:0,paddingInlineStart:0,"&::after":{display:"none"}},"&-subtitle":{display:"block",marginBottom:o.marginXXS,marginInlineStart:0,lineHeight:D}}),(0,r.Z)(f,"&"+v+"-small:not("+v+"-dot)",(0,r.Z)({},v+"-item",{"&-icon":{marginInlineStart:o.controlHeightLG+(g-P)/2}})),f))},Wt=Lt,$t=e(14747),Qt=function(o){var f,v,g,D,P,U=o.componentCls,fe=o.stepsNavContentMaxWidth,X=o.stepsNavArrowColor,w=o.stepsNavActiveColor,l=o.motionDurationSlow;return P={},(0,r.Z)(P,"&"+U+"-navigation",(g={paddingTop:o.paddingSM},(0,r.Z)(g,"&"+U+"-small",(0,r.Z)({},U+"-item",{"&-container":{marginInlineStart:-o.marginSM}})),(0,r.Z)(g,U+"-item",(v={overflow:"visible",textAlign:"center","&-container":(f={display:"inline-block",height:"100%",marginInlineStart:-o.margin,paddingBottom:o.paddingSM,textAlign:"start",transition:"opacity "+l},(0,r.Z)(f,U+"-item-content",{maxWidth:fe}),(0,r.Z)(f,U+"-item-title",(0,B.Z)((0,B.Z)({maxWidth:"100%",paddingInlineEnd:0},$t.vS),{"&::after":{display:"none"}})),f)},(0,r.Z)(v,"&:not("+U+"-item-active)",(0,r.Z)({},U+"-item-container[role='button']",{cursor:"pointer","&:hover":{opacity:.85}})),(0,r.Z)(v,"&:last-child",{flex:1,"&::after":{display:"none"}}),(0,r.Z)(v,"&::after",{position:"absolute",top:"calc(50% - "+o.paddingSM/2+"px)",insetInlineStart:"100%",display:"inline-block",width:o.fontSizeIcon,height:o.fontSizeIcon,borderTop:o.lineWidth+"px "+o.lineType+" "+X,borderBottom:"none",borderInlineStart:"none",borderInlineEnd:o.lineWidth+"px "+o.lineType+" "+X,transform:"translateY(-50%) translateX(-50%) rotate(45deg)",content:'""'}),(0,r.Z)(v,"&::before",{position:"absolute",bottom:0,insetInlineStart:"50%",display:"inline-block",width:0,height:o.lineWidthBold,backgroundColor:w,transition:"width "+l+", inset-inline-start "+l,transitionTimingFunction:"ease-out",content:'""'}),v)),(0,r.Z)(g,U+"-item"+U+"-item-active::before",{insetInlineStart:0,width:"100%"}),g)),(0,r.Z)(P,"&"+U+"-navigation"+U+"-vertical",(0,r.Z)({},"> "+U+"-item",(D={marginInlineEnd:0,"&::before":{display:"none"}},(0,r.Z)(D,"&"+U+"-item-active::before",{top:0,insetInlineEnd:0,insetInlineStart:"unset",display:"block",width:o.lineWidth*3,height:"calc(100% - "+o.marginLG+"px)"}),(0,r.Z)(D,"&::after",{position:"relative",insetInlineStart:"50%",display:"block",width:o.controlHeight*.25,height:o.controlHeight*.25,marginBottom:o.marginXS,textAlign:"center",transform:"translateY(-50%) translateX(-50%) rotate(135deg)"}),(0,r.Z)(D,"> "+U+"-item-container > "+U+"-item-tail",{visibility:"hidden"}),D))),(0,r.Z)(P,"&"+U+"-navigation"+U+"-horizontal",(0,r.Z)({},"> "+U+"-item > "+U+"-item-container > "+U+"-item-tail",{visibility:"hidden"})),P},mn=Qt,le=function(o){var f,v=o.antCls,g=o.componentCls;return(0,r.Z)({},"&"+g+"-with-progress",(f={},(0,r.Z)(f,g+"-item",(0,r.Z)({paddingTop:o.paddingXXS},"&-process "+g+"-item-container "+g+"-item-icon "+g+"-icon",{color:o.processIconColor})),(0,r.Z)(f,"&"+g+"-vertical > "+g+"-item ",(0,r.Z)({paddingInlineStart:o.paddingXXS},"> "+g+"-item-container > "+g+"-item-tail",{top:o.marginXXS,insetInlineStart:o.stepsIconSize/2-o.lineWidth+o.paddingXXS})),(0,r.Z)(f,"&, &"+g+"-small",(0,r.Z)({},"&"+g+"-horizontal "+g+"-item:first-child",{paddingBottom:o.paddingXXS,paddingInlineStart:o.paddingXXS})),(0,r.Z)(f,"&"+g+"-small"+g+"-vertical > "+g+"-item > "+g+"-item-container > "+g+"-item-tail",{insetInlineStart:o.stepsSmallIconSize/2-o.lineWidth+o.paddingXXS}),(0,r.Z)(f,"&"+g+"-label-vertical",(0,r.Z)({},g+"-item "+g+"-item-tail",{top:o.margin-2*o.lineWidth})),(0,r.Z)(f,g+"-item-icon",(0,r.Z)({position:"relative"},v+"-progress",{position:"absolute",insetBlockStart:(o.stepsIconSize-o.stepsProgressSize-o.lineWidth*2)/2,insetInlineStart:(o.stepsIconSize-o.stepsProgressSize-o.lineWidth*2)/2})),f))},We=le,He=function(o){var f,v,g,D,P=o.componentCls,U=o.descriptionWidth,fe=o.lineHeight,X=o.stepsCurrentDotSize,w=o.stepsDotSize,l=o.motionDurationSlow;return D={},(0,r.Z)(D,"&"+P+"-dot, &"+P+"-dot"+P+"-small",(0,r.Z)({},P+"-item",(f={"&-title":{lineHeight:fe},"&-tail":{top:Math.floor((o.stepsDotSize-o.lineWidth*3)/2),width:"100%",marginTop:0,marginBottom:0,marginInline:U/2+"px 0",padding:0,"&::after":{width:"calc(100% - "+o.marginSM*2+"px)",height:o.lineWidth*3,marginInlineStart:o.marginSM}},"&-icon":(0,r.Z)({width:w,height:w,marginInlineStart:(o.descriptionWidth-w)/2,paddingInlineEnd:0,lineHeight:w+"px",background:"transparent",border:0},P+"-icon-dot",{position:"relative",float:"left",width:"100%",height:"100%",borderRadius:100,transition:"all "+l,"&::after":{position:"absolute",top:-o.marginSM,insetInlineStart:(w-o.controlHeightLG*1.5)/2,width:o.controlHeightLG*1.5,height:o.controlHeight,background:"transparent",content:'""'}}),"&-content":{width:U}},(0,r.Z)(f,"&-process "+P+"-item-icon",{position:"relative",top:(w-X)/2,width:X,height:X,lineHeight:X+"px",background:"none",marginInlineStart:(o.descriptionWidth-X)/2}),(0,r.Z)(f,"&-process "+P+"-icon",(0,r.Z)({},"&:first-child "+P+"-icon-dot",{insetInlineStart:0})),f))),(0,r.Z)(D,"&"+P+"-vertical"+P+"-dot",(g={},(0,r.Z)(g,P+"-item-icon",{marginTop:(o.controlHeight-w)/2,marginInlineStart:0,background:"none"}),(0,r.Z)(g,P+"-item-process "+P+"-item-icon",{marginTop:(o.controlHeight-X)/2,top:0,insetInlineStart:(w-X)/2,marginInlineStart:0}),(0,r.Z)(g,P+"-item > "+P+"-item-container > "+P+"-item-tail",{top:(o.controlHeight-w)/2,insetInlineStart:0,margin:0,padding:w+o.paddingXS+"px 0 "+o.paddingXS+"px","&::after":{marginInlineStart:(w-o.lineWidth)/2}}),(0,r.Z)(g,"&"+P+"-small",(v={},(0,r.Z)(v,P+"-item-icon",{marginTop:(o.controlHeightSM-w)/2}),(0,r.Z)(v,P+"-item-process "+P+"-item-icon",{marginTop:(o.controlHeightSM-X)/2}),(0,r.Z)(v,P+"-item > "+P+"-item-container > "+P+"-item-tail",{top:(o.controlHeightSM-w)/2}),v)),(0,r.Z)(g,P+"-item:first-child "+P+"-icon-dot",{insetInlineStart:0}),(0,r.Z)(g,P+"-item-content",{width:"inherit"}),g)),D},Rt=He,zt=function(o){var f,v=o.componentCls;return(0,r.Z)({},"&"+v+"-rtl",(f={direction:"rtl"},(0,r.Z)(f,v+"-item",{"&-subtitle":{float:"left"}}),(0,r.Z)(f,"&"+v+"-navigation",(0,r.Z)({},v+"-item::after",{transform:"rotate(-45deg)"})),(0,r.Z)(f,"&"+v+"-vertical",(0,r.Z)({},"> "+v+"-item",(0,r.Z)({"&::after":{transform:"rotate(225deg)"}},v+"-item-icon",{float:"right"}))),(0,r.Z)(f,"&"+v+"-dot",(0,r.Z)({},v+"-item-icon "+v+"-icon-dot, &"+v+"-small "+v+"-item-icon "+v+"-icon-dot",{float:"right"})),f))},wt=zt,Xe=function(o){var f,v=o.componentCls,g=o.stepsSmallIconSize,D=o.fontSizeSM,P=o.fontSize,U=o.colorTextDescription;return(0,r.Z)({},"&"+v+"-small",(f={},(0,r.Z)(f,"&"+v+"-horizontal:not("+v+"-label-vertical) "+v+"-item",{paddingInlineStart:o.paddingSM,"&:first-child":{paddingInlineStart:0}}),(0,r.Z)(f,v+"-item-icon",{width:g,height:g,marginTop:0,marginBottom:0,marginInline:"0 "+o.marginXS+"px",fontSize:D,lineHeight:g+"px",textAlign:"center",borderRadius:g}),(0,r.Z)(f,v+"-item-title",{paddingInlineEnd:o.paddingSM,fontSize:P,lineHeight:g+"px","&::after":{top:g/2}}),(0,r.Z)(f,v+"-item-description",{color:U,fontSize:P}),(0,r.Z)(f,v+"-item-tail",{top:g/2-o.paddingXXS}),(0,r.Z)(f,v+"-item-custom "+v+"-item-icon",(0,r.Z)({width:"inherit",height:"inherit",lineHeight:"inherit",background:"none",border:0,borderRadius:0},"> "+v+"-icon",{fontSize:g,lineHeight:g+"px",transform:"none"})),f))},ft=Xe,gt=function(o){var f,v,g,D=o.componentCls,P=o.stepsSmallIconSize,U=o.stepsIconSize;return(0,r.Z)({},"&"+D+"-vertical",(g={display:"flex",flexDirection:"column"},(0,r.Z)(g,"> "+D+"-item",(f={display:"block",flex:"1 0 auto",paddingInlineStart:0,overflow:"visible"},(0,r.Z)(f,D+"-item-icon",{float:"left",marginInlineEnd:o.margin}),(0,r.Z)(f,D+"-item-content",{display:"block",minHeight:o.controlHeight*1.5,overflow:"hidden"}),(0,r.Z)(f,D+"-item-title",{lineHeight:U+"px"}),(0,r.Z)(f,D+"-item-description",{paddingBottom:o.paddingSM}),f)),(0,r.Z)(g,"> "+D+"-item > "+D+"-item-container > "+D+"-item-tail",{position:"absolute",top:0,insetInlineStart:o.stepsIconSize/2-o.lineWidth,width:o.lineWidth,height:"100%",padding:U+o.marginXXS*1.5+"px 0 "+o.marginXXS*1.5+"px","&::after":{width:o.lineWidth,height:"100%"}}),(0,r.Z)(g,"> "+D+"-item:not(:last-child) > "+D+"-item-container > "+D+"-item-tail",{display:"block"}),(0,r.Z)(g," > "+D+"-item > "+D+"-item-container > "+D+"-item-content > "+D+"-item-title",{"&::after":{display:"none"}}),(0,r.Z)(g,"&"+D+"-small "+D+"-item-container",(v={},(0,r.Z)(v,D+"-item-tail",{position:"absolute",top:0,insetInlineStart:o.stepsSmallIconSize/2-o.lineWidth,padding:P+o.marginXXS*1.5+"px 0 "+o.marginXXS*1.5+"px"}),(0,r.Z)(v,D+"-item-title",{lineHeight:P+"px"}),v)),g))},_t=gt,Be=function(o){var f,v,g,D=o.componentCls,P=o.inlineDotSize,U=o.inlineTitleColor,fe=o.inlineTailColor,X=o.paddingXS+o.lineWidth,w=(0,r.Z)({},D+"-item-container "+D+"-item-content "+D+"-item-title",{color:U});return(0,r.Z)({},"&"+D+"-inline",(0,r.Z)({width:"auto",display:"inline-flex"},D+"-item",(g={flex:"none","&-container":(0,r.Z)({padding:X+"px "+o.paddingXXS+"px 0",margin:"0 "+o.marginXXS/2+"px",borderRadius:o.borderRadiusSM,cursor:"pointer",transition:"background-color "+o.motionDurationMid,"&:hover":{background:o.controlItemBgHover}},"&[role='button']:hover",{opacity:1}),"&-icon":(f={width:P,height:P,marginInlineStart:"calc(50% - "+P/2+"px)"},(0,r.Z)(f,"> "+D+"-icon",{top:0}),(0,r.Z)(f,D+"-icon-dot",{borderRadius:o.fontSizeSM/4}),f),"&-content":{width:"auto",marginTop:o.marginXS-o.lineWidth},"&-title":{color:U,fontSize:o.fontSizeSM,lineHeight:o.lineHeightSM,fontWeight:"normal",marginBottom:o.marginXXS/2},"&-description":{display:"none"},"&-tail":{marginInlineStart:0,top:X+P/2,transform:"translateY(-50%)","&:after":{width:"100%",height:o.lineWidth,borderRadius:0,marginInlineStart:0,background:fe}}},(0,r.Z)(g,"&:first-child "+D+"-item-tail",{width:"50%",marginInlineStart:"50%"}),(0,r.Z)(g,"&:last-child "+D+"-item-tail",{display:"block",width:"50%"}),(0,r.Z)(g,"&-wait",(0,B.Z)((0,r.Z)({},D+"-item-icon "+D+"-icon "+D+"-icon-dot",{backgroundColor:o.colorBorderBg,border:o.lineWidth+"px "+o.lineType+" "+fe}),w)),(0,r.Z)(g,"&-finish",(0,B.Z)((v={},(0,r.Z)(v,D+"-item-tail::after",{backgroundColor:fe}),(0,r.Z)(v,D+"-item-icon "+D+"-icon "+D+"-icon-dot",{backgroundColor:fe,border:o.lineWidth+"px "+o.lineType+" "+fe}),v),w)),(0,r.Z)(g,"&-error",w),(0,r.Z)(g,"&-active, &-process",(0,B.Z)((0,r.Z)({},D+"-item-icon",{width:P,height:P,marginInlineStart:"calc(50% - "+P/2+"px)",top:0}),w)),(0,r.Z)(g,"&:not("+D+"-item-active) > "+D+"-item-container[role='button']:hover",(0,r.Z)({},D+"-item-title",{color:U})),g)))},ce=Be,Ge;(function(d){d.wait="wait",d.process="process",d.finish="finish",d.error="error"})(Ge||(Ge={}));var Ye=function(o,f){var v,g=f.componentCls+"-item",D=o+"IconColor",P=o+"TitleColor",U=o+"DescriptionColor",fe=o+"TailColor",X=o+"IconBgColor",w=o+"IconBorderColor",l=o+"DotColor";return v={},(0,r.Z)(v,g+"-"+o+" "+g+"-icon",(0,r.Z)({backgroundColor:f[X],borderColor:f[w]},"> "+f.componentCls+"-icon",(0,r.Z)({color:f[D]},f.componentCls+"-icon-dot",{background:f[l]}))),(0,r.Z)(v,g+"-"+o+g+"-custom "+g+"-icon",(0,r.Z)({},"> "+f.componentCls+"-icon",{color:f[l]})),(0,r.Z)(v,g+"-"+o+" > "+g+"-container > "+g+"-content > "+g+"-title",{color:f[P],"&::after":{backgroundColor:f[fe]}}),(0,r.Z)(v,g+"-"+o+" > "+g+"-container > "+g+"-content > "+g+"-description",{color:f[U]}),(0,r.Z)(v,g+"-"+o+" > "+g+"-container > "+g+"-tail::after",{backgroundColor:f[fe]}),v},Ot=function(o){var f,v,g=o.componentCls,D=o.motionDurationSlow,P=g+"-item";return(0,B.Z)((0,B.Z)((0,B.Z)((0,B.Z)((0,B.Z)((0,B.Z)((f={},(0,r.Z)(f,P,{position:"relative",display:"inline-block",flex:1,overflow:"hidden",verticalAlign:"top","&:last-child":(0,r.Z)({flex:"none"},"> "+P+"-container > "+P+"-tail, > "+P+"-container > "+P+"-content > "+P+"-title::after",{display:"none"})}),(0,r.Z)(f,P+"-container",{outline:"none"}),(0,r.Z)(f,P+"-icon, "+P+"-content",{display:"inline-block",verticalAlign:"top"}),(0,r.Z)(f,P+"-icon",(0,r.Z)({width:o.stepsIconSize,height:o.stepsIconSize,marginTop:0,marginBottom:0,marginInlineStart:0,marginInlineEnd:o.marginXS,fontSize:o.stepsIconFontSize,fontFamily:o.fontFamily,lineHeight:o.stepsIconSize+"px",textAlign:"center",borderRadius:o.stepsIconSize,border:o.lineWidth+"px "+o.lineType+" transparent",transition:"background-color "+D+", border-color "+D},g+"-icon",{position:"relative",top:o.stepsIconTop,color:o.colorPrimary,lineHeight:1})),(0,r.Z)(f,P+"-tail",{position:"absolute",top:o.stepsIconSize/2-o.paddingXXS,insetInlineStart:0,width:"100%","&::after":{display:"inline-block",width:"100%",height:o.lineWidth,background:o.colorSplit,borderRadius:o.lineWidth,transition:"background "+D,content:'""'}}),(0,r.Z)(f,P+"-title",{position:"relative",display:"inline-block",paddingInlineEnd:o.padding,color:o.colorText,fontSize:o.fontSizeLG,lineHeight:o.stepsTitleLineHeight+"px","&::after":{position:"absolute",top:o.stepsTitleLineHeight/2,insetInlineStart:"100%",display:"block",width:9999,height:o.lineWidth,background:o.processTailColor,content:'""'}}),(0,r.Z)(f,P+"-subtitle",{display:"inline",marginInlineStart:o.marginXS,color:o.colorTextDescription,fontWeight:"normal",fontSize:o.fontSize}),(0,r.Z)(f,P+"-description",{color:o.colorTextDescription,fontSize:o.fontSize}),f),Ye(Ge.wait,o)),Ye(Ge.process,o)),(0,r.Z)({},P+"-process > "+P+"-container > "+P+"-title",{fontWeight:o.fontWeightStrong})),Ye(Ge.finish,o)),Ye(Ge.error,o)),(v={},(0,r.Z)(v,""+P+g+"-next-error > "+g+"-item-title::after",{background:o.colorError}),(0,r.Z)(v,P+"-disabled",{cursor:"not-allowed"}),v))},z=function(o){var f,v,g,D,P=o.componentCls,U=o.motionDurationSlow;return D={},(0,r.Z)(D,"& "+P+"-item",(0,r.Z)({},"&:not("+P+"-item-active)",(v={},(0,r.Z)(v,"& > "+P+"-item-container[role='button']",(f={cursor:"pointer"},(0,r.Z)(f,P+"-item",(0,r.Z)({},"&-title, &-subtitle, &-description, &-icon "+P+"-icon",{transition:"color "+U})),(0,r.Z)(f,"&:hover",(0,r.Z)({},P+"-item",(0,r.Z)({},"&-title, &-subtitle, &-description",{color:o.colorPrimary}))),f)),(0,r.Z)(v,"&:not("+P+"-item-process)",(0,r.Z)({},"& > "+P+"-item-container[role='button']:hover",(0,r.Z)({},P+"-item",{"&-icon":(0,r.Z)({borderColor:o.colorPrimary},P+"-icon",{color:o.colorPrimary})}))),v))),(0,r.Z)(D,"&"+P+"-horizontal:not("+P+"-label-vertical)",(0,r.Z)({},P+"-item",(g={paddingInlineStart:o.padding,whiteSpace:"nowrap","&:first-child":{paddingInlineStart:0}},(0,r.Z)(g,"&:last-child "+P+"-item-title",{paddingInlineEnd:0}),(0,r.Z)(g,"&-tail",{display:"none"}),(0,r.Z)(g,"&-description",{maxWidth:o.descriptionWidth,whiteSpace:"normal"}),g))),D},V=function(o){var f=o.componentCls;return(0,r.Z)({},f,(0,B.Z)((0,B.Z)((0,B.Z)((0,B.Z)((0,B.Z)((0,B.Z)((0,B.Z)((0,B.Z)((0,B.Z)((0,B.Z)((0,B.Z)((0,B.Z)((0,B.Z)({},(0,$t.Wf)(o)),{display:"flex",width:"100%",fontSize:0,textAlign:"initial"}),Ot(o)),z(o)),Zt(o)),ft(o)),_t(o)),Wt(o)),Rt(o)),mn(o)),wt(o)),We(o)),ce(o)))},k=(0,qe.Z)("Steps",function(d){var o=d.wireframe,f=d.colorTextDisabled,v=d.fontSizeHeading3,g=d.fontSize,D=d.controlHeight,P=d.controlHeightLG,U=d.colorTextLightSolid,fe=d.colorText,X=d.colorPrimary,w=d.colorTextLabel,l=d.colorTextDescription,G=d.colorTextQuaternary,ne=d.colorFillContent,q=d.controlItemBgActive,Y=d.colorError,ot=d.colorBgContainer,at=d.colorBorderSecondary,ct=d.controlHeight,Qe=d.colorSplit,Le=(0,A.TS)(d,{processTailColor:Qe,stepsNavArrowColor:f,stepsIconSize:ct,stepsIconCustomSize:ct,stepsIconCustomTop:0,stepsIconCustomFontSize:P/2,stepsIconTop:-.5,stepsIconFontSize:g,stepsTitleLineHeight:D,stepsSmallIconSize:v,stepsDotSize:D/4,stepsCurrentDotSize:P/4,stepsNavContentMaxWidth:"auto",processIconColor:U,processTitleColor:fe,processDescriptionColor:fe,processIconBgColor:X,processIconBorderColor:X,processDotColor:X,waitIconColor:o?f:w,waitTitleColor:l,waitDescriptionColor:l,waitTailColor:Qe,waitIconBgColor:o?ot:ne,waitIconBorderColor:o?f:"transparent",waitDotColor:f,finishIconColor:X,finishTitleColor:fe,finishDescriptionColor:l,finishTailColor:X,finishIconBgColor:o?ot:q,finishIconBorderColor:o?X:q,finishDotColor:X,errorIconColor:U,errorTitleColor:Y,errorDescriptionColor:Y,errorTailColor:Qe,errorIconBgColor:Y,errorIconBorderColor:Y,errorDotColor:Y,stepsNavActiveColor:X,stepsProgressSize:P,inlineDotSize:6,inlineTitleColor:G,inlineTailColor:at});return[V(Le)]},{descriptionWidth:140}),Ke=function(d,o){var f={};for(var v in d)Object.prototype.hasOwnProperty.call(d,v)&&o.indexOf(v)<0&&(f[v]=d[v]);if(d!=null&&typeof Object.getOwnPropertySymbols=="function")for(var g=0,v=Object.getOwnPropertySymbols(d);g form":{maxWidth:"100%"}}})};function xe(d){return(0,Vt.Xj)("StepsForm",function(o){var f=(0,a.Z)((0,a.Z)({},o),{},{componentCls:".".concat(d)});return[Ze(f)]})}var he=["current","onCurrentChange","submitter","stepsFormRender","stepsRender","stepFormRender","stepsProps","onFinish","formProps","containerStyle","formRef","formMapRef"],oe=M.createContext(void 0),Dt={horizontal:function(o){var f=o.stepsDom,v=o.formDom;return(0,j.jsxs)(j.Fragment,{children:[(0,j.jsx)(ye.Z,{gutter:{xs:8,sm:16,md:24},children:(0,j.jsx)(Ce.Z,{span:24,children:f})}),(0,j.jsx)(ye.Z,{gutter:{xs:8,sm:16,md:24},children:(0,j.jsx)(Ce.Z,{span:24,children:v})})]})},vertical:function(o){var f=o.stepsDom,v=o.formDom;return(0,j.jsxs)(ye.Z,{align:"stretch",wrap:!0,gutter:{xs:8,sm:16,md:24},children:[(0,j.jsx)(Ce.Z,{xxl:4,xl:6,lg:7,md:8,sm:10,xs:12,children:M.cloneElement(f,{style:{height:"100%"}})}),(0,j.jsx)(Ce.Z,{children:(0,j.jsx)("div",{style:{display:"flex",alignItems:"center",width:"100%",height:"100%"},children:v})})]})}};function Kn(d){var o=(0,M.useContext)(je.ZP.ConfigContext),f=o.getPrefixCls,v=f("pro-steps-form"),g=xe(v),D=g.wrapSSR,P=g.hashId,U=d.current,fe=d.onCurrentChange,X=d.submitter,w=d.stepsFormRender,l=d.stepsRender,G=d.stepFormRender,ne=d.stepsProps,q=d.onFinish,Y=d.formProps,ot=d.containerStyle,at=d.formRef,ct=d.formMapRef,Qe=(0,t.Z)(d,he),Le=(0,M.useRef)(new Map),Ue=(0,M.useRef)(new Map),ut=(0,M.useRef)([]),ht=(0,M.useState)([]),Pt=(0,$.Z)(ht,2),yt=Pt[0],en=Pt[1],Bt=(0,M.useState)(!1),an=(0,$.Z)(Bt,2),Nt=an[0],Xt=an[1],on=(0,T.YB)(),it=(0,$e.Z)(0,{value:d.current,onChange:d.onCurrentChange}),Ne=(0,$.Z)(it,2),et=Ne[0],un=Ne[1],Tn=(0,M.useMemo)(function(){return Dt[(ne==null?void 0:ne.direction)||"horizontal"]},[ne==null?void 0:ne.direction]),An=(0,M.useMemo)(function(){return et===yt.length-1},[yt.length,et]),ir=(0,M.useCallback)(function(y,I){Ue.current.has(y)||en(function(E){return[].concat((0,N.Z)(E),[y])}),Ue.current.set(y,I)},[]),Yn=(0,M.useCallback)(function(y){en(function(I){return I.filter(function(E){return E!==y})}),Ue.current.delete(y),Le.current.delete(y)},[]);(0,M.useImperativeHandle)(ct,function(){return ut.current}),(0,M.useImperativeHandle)(at,function(){var y;return(y=ut.current[et||0])===null||y===void 0?void 0:y.current},[et]);var Mn=(0,M.useCallback)(function(){var y=(0,C.Z)((0,F.Z)().mark(function I(E,se){var J,_;return(0,F.Z)().wrap(function(jt){for(;;)switch(jt.prev=jt.next){case 0:if(Le.current.set(E,se),!(!An||!q)){jt.next=3;break}return jt.abrupt("return");case 3:return Xt(!0),J=h.T.apply(void 0,[{}].concat((0,N.Z)(Array.from(Le.current.values())))),jt.prev=5,jt.next=8,q(J);case 8:_=jt.sent,_&&(un(0),ut.current.forEach(function(rt){var Yt;return(Yt=rt.current)===null||Yt===void 0?void 0:Yt.resetFields()})),jt.next=15;break;case 12:jt.prev=12,jt.t0=jt.catch(5),console.log(jt.t0);case 15:return jt.prev=15,Xt(!1),jt.finish(15);case 18:case"end":return jt.stop()}},I,null,[[5,12,15,18]])}));return function(I,E){return y.apply(this,arguments)}}(),[An,q,Xt,un]),Me=(0,M.useMemo)(function(){var y=(0,Z.n)(L.Z,"4.24.0")>-1,I=y?{items:yt.map(function(E){var se=Ue.current.get(E);return(0,a.Z)({key:E,title:se==null?void 0:se.title},se==null?void 0:se.stepProps)})}:{};return(0,j.jsx)("div",{className:"".concat(v,"-steps-container ").concat(P),style:{maxWidth:Math.min(yt.length*320,1160)},children:(0,j.jsx)(qt,(0,a.Z)((0,a.Z)((0,a.Z)({},ne),I),{},{current:et,onChange:void 0,children:!y&&yt.map(function(E){var se=Ue.current.get(E);return(0,j.jsx)(qt.Step,(0,a.Z)({title:se==null?void 0:se.title},se==null?void 0:se.stepProps),E)})}))})},[yt,P,v,et,ne]),bt=(0,pe.J)(function(){var y,I=ut.current[et];(y=I.current)===null||y===void 0||y.submit()}),Zn=(0,pe.J)(function(){et<1||un(et-1)}),tn=(0,M.useMemo)(function(){return X!==!1&&(0,j.jsx)(Sn.Z,(0,a.Z)((0,a.Z)({type:"primary",loading:Nt},X==null?void 0:X.submitButtonProps),{},{onClick:function(){var I;X==null||(I=X.onSubmit)===null||I===void 0||I.call(X),bt()},children:on.getMessage("stepsForm.next","\u4E0B\u4E00\u6B65")}),"next")},[on,Nt,bt,X]),Xn=(0,M.useMemo)(function(){return X!==!1&&(0,j.jsx)(Sn.Z,(0,a.Z)((0,a.Z)({},X==null?void 0:X.resetButtonProps),{},{onClick:function(){var I;Zn(),X==null||(I=X.onReset)===null||I===void 0||I.call(X)},children:on.getMessage("stepsForm.prev","\u4E0A\u4E00\u6B65")}),"pre")},[on,Zn,X]),In=(0,M.useMemo)(function(){return X!==!1&&(0,j.jsx)(Sn.Z,(0,a.Z)((0,a.Z)({type:"primary",loading:Nt},X==null?void 0:X.submitButtonProps),{},{onClick:function(){var I;X==null||(I=X.onSubmit)===null||I===void 0||I.call(X),bt()},children:on.getMessage("stepsForm.submit","\u63D0\u4EA4")}),"submit")},[on,Nt,bt,X]),zn=(0,pe.J)(function(){et>yt.length-2||un(et+1)}),Qn=(0,M.useMemo)(function(){var y=[],I=et||0;if(I<1?y.push(tn):I+1===yt.length?y.push(Xn,In):y.push(Xn,tn),y=y.filter(M.isValidElement),X&&X.render){var E,se={form:(E=ut.current[et])===null||E===void 0?void 0:E.current,onSubmit:bt,step:et,onPre:Zn};return X.render(se,y)}return X&&(X==null?void 0:X.render)===!1?null:y},[yt.length,tn,bt,Xn,Zn,et,In,X]),qn=(0,M.useMemo)(function(){return(0,Dn.Z)(d.children).map(function(y,I){var E=y.props,se=E.name||"".concat(I),J=et===I,_=J?{contentRender:G,submitter:!1}:{};return(0,j.jsx)("div",{className:ee()("".concat(v,"-step"),P,(0,r.Z)({},"".concat(v,"-step-active"),J)),children:M.cloneElement(y,(0,a.Z)((0,a.Z)((0,a.Z)((0,a.Z)({},_),Y),E),{},{name:se,step:I,key:se}))},se)})},[Y,P,v,d.children,et,G]),m=(0,M.useMemo)(function(){return l?l(yt.map(function(y){var I;return{key:y,title:(I=Ue.current.get(y))===null||I===void 0?void 0:I.title}}),Me):Me},[yt,Me,l]),s=(0,M.useMemo)(function(){return(0,j.jsxs)("div",{className:"".concat(v,"-container ").concat(P),style:ot,children:[qn,w?null:(0,j.jsx)(bn.Z,{children:Qn})]})},[ot,qn,P,v,w,Qn]),R=(0,M.useMemo)(function(){var y={stepsDom:m,formDom:s};return w?w(Tn(y),Qn):Tn(y)},[m,s,Tn,w,Qn]);return D((0,j.jsx)("div",{className:ee()(v,P),children:(0,j.jsx)(Un.Z.Provider,(0,a.Z)((0,a.Z)({},Qe),{},{children:(0,j.jsx)(oe.Provider,{value:{loading:Nt,setLoading:Xt,regForm:ir,keyArray:yt,next:zn,formArrayRef:ut,formMapRef:Ue,lastStep:An,unRegForm:Yn,onFormFinish:Mn},children:R})}))}))}function b(d){return(0,j.jsx)(T._Y,{needDeps:!0,children:(0,j.jsx)(Kn,(0,a.Z)({},d))})}b.StepForm=Ut,b.useForm=Un.Z.useForm},1914:function(Cn,ve,e){"use strict";e.d(ve,{Z:function(){return Ae}});var r=e(74165),a=e(15861),F=e(71002),C=e(97685),N=e(4942),$=e(74902),t=e(1413),j=e(91),T=e(67294),h=e(85893),Z=e(84517),pe=e(952),L=e(73964),ye=e(86671),Ce=e(51812),je=e(60869),B=e(27068),H=e(78164),Se=e(27414),we=e(46986),ee=e(94184),M=e.n(ee),Ie=e(30939),ue=e(26713),Ee=e(14855),_e=function(n){return(0,N.Z)({},n.componentCls,{marginBlockEnd:16,backgroundColor:(0,Ee.uK)(n.colorTextBase,.02),borderRadius:n.borderRadius,border:"none","&-container":{paddingBlock:n.paddingSM,paddingInline:n.paddingLG},"&-info":{display:"flex",alignItems:"center",transition:"all 0.3s",color:n.colorTextTertiary,"&-content":{flex:1},"&-option":{minWidth:48,paddingInlineStart:16}}})};function xt(u){return(0,Ee.Xj)("ProTableAlert",function(n){var p=(0,t.Z)((0,t.Z)({},n),{},{componentCls:".".concat(u)});return[_e(p)]})}var be=function(n){var p=n.intl,c=n.onCleanSelected;return[(0,h.jsx)("a",{onClick:c,children:p.getMessage("alert.clear","\u6E05\u7A7A")},"0")]};function Nn(u){var n=u.selectedRowKeys,p=n===void 0?[]:n,c=u.onCleanSelected,S=u.alwaysShowAlert,K=u.selectedRows,ze=u.alertInfoRender,W=ze===void 0?function(Gt){var At=Gt.intl;return(0,h.jsxs)(ue.Z,{children:[At.getMessage("alert.selected","\u5DF2\u9009\u62E9"),p.length,At.getMessage("alert.item","\u9879"),"\xA0\xA0"]})}:ze,Q=u.alertOptionRender,ae=Q===void 0?be:Q,nt=(0,L.YB)(),st=ae&&ae({onCleanSelected:c,selectedRowKeys:p,selectedRows:K,intl:nt}),ie=(0,T.useContext)(we.ZP.ConfigContext),de=ie.getPrefixCls,Fe=de("pro-table-alert"),Ft=xt(Fe),re=Ft.wrapSSR,Tt=Ft.hashId;if(W===!1)return null;var ln=W({intl:nt,selectedRowKeys:p,selectedRows:K,onCleanSelected:c});return ln===!1||p.length<1&&!S?null:re((0,h.jsx)("div",{className:"".concat(Fe," ").concat(Tt),children:(0,h.jsx)("div",{className:"".concat(Fe,"-container ").concat(Tt),children:(0,h.jsxs)("div",{className:"".concat(Fe,"-info ").concat(Tt),children:[(0,h.jsx)("div",{className:"".concat(Fe,"-info-content ").concat(Tt),children:ln}),st?(0,h.jsx)("div",{className:"".concat(Fe,"-info-option ").concat(Tt),children:st}):null]})})}))}var ge=Nn,me=e(43144),Oe=e(15671),dt=e(60136),Te=e(29388),ke=e(60249),Kt=e(97435),Pn=function(n){return n!=null};function qe(u,n,p){var c,S;if(u===!1)return!1;var K=n.total,ze=n.current,W=n.pageSize,Q=n.setPageInfo,ae=(0,F.Z)(u)==="object"?u:{};return(0,t.Z)((0,t.Z)({showTotal:function(st,ie){return"".concat(p.getMessage("pagination.total.range","\u7B2C")," ").concat(ie[0],"-").concat(ie[1]," ").concat(p.getMessage("pagination.total.total","\u6761/\u603B\u5171")," ").concat(st," ").concat(p.getMessage("pagination.total.item","\u6761"))},total:K},ae),{},{current:u!==!0&&u&&(c=u.current)!==null&&c!==void 0?c:ze,pageSize:u!==!0&&u&&(S=u.pageSize)!==null&&S!==void 0?S:W,onChange:function(st,ie){var de=u.onChange;de==null||de(st,ie||20),(ie!==W||ze!==st)&&Q({pageSize:ie,current:st})}})}function A(u,n,p){var c=(0,t.Z)((0,t.Z)({},p.editableUtils),{},{pageInfo:n.pageInfo,reload:function(){var S=(0,a.Z)((0,r.Z)().mark(function ze(W){return(0,r.Z)().wrap(function(ae){for(;;)switch(ae.prev=ae.next){case 0:if(!W){ae.next=3;break}return ae.next=3,n.setPageInfo({current:1});case 3:return ae.next=5,n==null?void 0:n.reload();case 5:case"end":return ae.stop()}},ze)}));function K(ze){return S.apply(this,arguments)}return K}(),reloadAndRest:function(){var S=(0,a.Z)((0,r.Z)().mark(function ze(){return(0,r.Z)().wrap(function(Q){for(;;)switch(Q.prev=Q.next){case 0:return p.onCleanSelected(),Q.next=3,n.setPageInfo({current:1});case 3:return Q.next=5,n==null?void 0:n.reload();case 5:case"end":return Q.stop()}},ze)}));function K(){return S.apply(this,arguments)}return K}(),reset:function(){var S=(0,a.Z)((0,r.Z)().mark(function ze(){var W;return(0,r.Z)().wrap(function(ae){for(;;)switch(ae.prev=ae.next){case 0:return ae.next=2,p.resetAll();case 2:return ae.next=4,n==null||(W=n.reset)===null||W===void 0?void 0:W.call(n);case 4:return ae.next=6,n==null?void 0:n.reload();case 6:case"end":return ae.stop()}},ze)}));function K(){return S.apply(this,arguments)}return K}(),fullScreen:function(){return p.fullScreen()},clearSelected:function(){return p.onCleanSelected()},setPageInfo:function(K){return n.setPageInfo(K)}});u.current=c}function pt(u,n){return n.filter(function(p){return p}).length<1?u:n.reduce(function(p,c){return c(p)},u)}var Zt=function(n,p){return p===void 0?!1:typeof p=="boolean"?p:p[n]},Lt=function(n){var p;return n&&(0,F.Z)(n)==="object"&&(n==null||(p=n.props)===null||p===void 0?void 0:p.colSpan)},Wt=function(n,p){return n?Array.isArray(n)?n.join("-"):n.toString():"".concat(p)};function $t(u){return Array.isArray(u)?u.join(","):u==null?void 0:u.toString()}function Qt(u){var n={},p={};return u.forEach(function(c){var S=$t(c.dataIndex);if(!!S){if(c.filters){var K=c.defaultFilteredValue;K===void 0?n[S]=null:n[S]=c.defaultFilteredValue}c.sorter&&c.defaultSortOrder&&(p[S]=c.defaultSortOrder)}}),{sort:p,filter:n}}function mn(u,n){var p=u.oldIndex,c=u.newIndex;if(p!==c){var S=arrayMoveImmutable({array:_toConsumableArray(n||[]),fromIndex:p,toIndex:c}).filter(function(K){return!!K});return _toConsumableArray(S)}return null}var le=e(59022);function We(u){var n=u.replace(/[A-Z]/g,function(p){return"-".concat(p.toLowerCase())});return n.startsWith("-")&&(n=n.slice(1)),n}var He=function(n,p){return!n&&p!==!1?(p==null?void 0:p.filterType)==="light"?"LightFilter":"QueryFilter":"Form"},Rt=function(n,p,c){return!n&&c==="LightFilter"?(0,Kt.Z)((0,t.Z)({},p),["labelWidth","defaultCollapsed","filterType"]):n?{}:(0,Kt.Z)((0,t.Z)({labelWidth:p?p==null?void 0:p.labelWidth:void 0,defaultCollapsed:!0},p),["filterType"])},zt=function(n,p){return n?(0,Kt.Z)(p,["ignoreRules"]):(0,t.Z)({ignoreRules:!0},p)},wt=function(n){var p,c=n.onSubmit,S=n.formRef,K=n.dateFormatter,ze=K===void 0?"string":K,W=n.type,Q=n.columns,ae=n.action,nt=n.ghost,st=n.manualRequest,ie=n.onReset,de=n.submitButtonLoading,Fe=n.search,Ft=n.form,re=n.bordered,Tt=(0,T.useContext)(L.L_),ln=Tt.hashId,Gt=W==="form",At=function(){var St=(0,a.Z)((0,r.Z)().mark(function Mt(lt,vn){return(0,r.Z)().wrap(function(Pe){for(;;)switch(Pe.prev=Pe.next){case 0:c&&c(lt,vn);case 1:case"end":return Pe.stop()}},Mt)}));return function(lt,vn){return St.apply(this,arguments)}}(),Bn=(0,T.useContext)(we.ZP.ConfigContext),It=Bn.getPrefixCls,xn=(0,T.useMemo)(function(){return Q.filter(function(St){return!(St===Se.Z.EXPAND_COLUMN||St===Se.Z.SELECTION_COLUMN||(St.hideInSearch||St.search===!1)&&W!=="form"||W==="form"&&St.hideInForm)}).map(function(St){var Mt,lt=!St.valueType||["textarea","jsonCode","code"].includes(St==null?void 0:St.valueType)&&W==="table"?"text":St==null?void 0:St.valueType,vn=(St==null?void 0:St.key)||(St==null||(Mt=St.dataIndex)===null||Mt===void 0?void 0:Mt.toString());return(0,t.Z)((0,t.Z)((0,t.Z)({},St),{},{width:void 0},St.search?St.search:{}),{},{valueType:lt,proFieldProps:(0,t.Z)((0,t.Z)({},St.proFieldProps),{},{proFieldKey:vn?"table-field-".concat(vn):void 0})})})},[Q,W]),nn=It("pro-table-search"),te=It("pro-table-form"),Et=(0,T.useMemo)(function(){return He(Gt,Fe)},[Fe,Gt]),fn=(0,T.useMemo)(function(){return{submitter:{submitButtonProps:{loading:de}}}},[de]);return(0,h.jsx)("div",{className:M()(ln,(p={},(0,N.Z)(p,It("pro-card"),!0),(0,N.Z)(p,"".concat(It("pro-card"),"-border"),!!re),(0,N.Z)(p,"".concat(It("pro-card"),"-bordered"),!!re),(0,N.Z)(p,"".concat(It("pro-card"),"-ghost"),!!nt),(0,N.Z)(p,nn,!0),(0,N.Z)(p,te,Gt),(0,N.Z)(p,It("pro-table-search-".concat(We(Et))),!0),(0,N.Z)(p,"".concat(nn,"-ghost"),nt),(0,N.Z)(p,Fe==null?void 0:Fe.className,Fe!==!1&&(Fe==null?void 0:Fe.className)),p)),children:(0,h.jsx)(le.Z,(0,t.Z)((0,t.Z)((0,t.Z)((0,t.Z)({layoutType:Et,columns:xn,type:W},fn),Rt(Gt,Fe,Et)),zt(Gt,Ft||{})),{},{formRef:S,action:ae,dateFormatter:ze,onInit:function(Mt){if(W!=="form"){var lt,vn,Ht,Pe=(lt=ae.current)===null||lt===void 0?void 0:lt.pageInfo,rn=Mt.current,jn=rn===void 0?Pe==null?void 0:Pe.current:rn,sr=Mt.pageSize,ur=sr===void 0?Pe==null?void 0:Pe.pageSize:sr;if((vn=ae.current)===null||vn===void 0||(Ht=vn.setPageInfo)===null||Ht===void 0||Ht.call(vn,(0,t.Z)((0,t.Z)({},Pe),{},{current:parseInt(jn,10),pageSize:parseInt(ur,10)})),st)return;At(Mt,!0)}},onReset:function(Mt){ie==null||ie(Mt)},onFinish:function(Mt){At(Mt,!1)},initialValues:Ft==null?void 0:Ft.initialValues}))})},Xe=wt,ft=function(u){(0,dt.Z)(p,u);var n=(0,Te.Z)(p);function p(){var c;(0,Oe.Z)(this,p);for(var S=arguments.length,K=new Array(S),ze=0;ze");return S}return{Provider:p,useContainer:c}}function Ke(u){return u.useContainer()}function vt(){var u,n,p,c,S=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},K=(0,T.useRef)(),ze=(0,T.useRef)(null),W=(0,T.useRef)(),Q=(0,T.useRef)(),ae=(0,T.useState)(""),nt=(0,C.Z)(ae,2),st=nt[0],ie=nt[1],de=(0,T.useRef)([]),Fe=(0,je.Z)(function(){return S.size||S.defaultSize||"middle"},{value:S.size,onChange:S.onSizeChange}),Ft=(0,C.Z)(Fe,2),re=Ft[0],Tt=Ft[1],ln=(0,T.useMemo)(function(){var te,Et={};return(te=S.columns)===null||te===void 0||te.forEach(function(fn,St){var Mt=fn.key,lt=fn.dataIndex,vn=fn.fixed,Ht=fn.disable,Pe=Wt(Mt!=null?Mt:lt,St);Pe&&(Et[Pe]={show:!0,fixed:vn,disable:Ht})}),Et},[S.columns]),Gt=(0,je.Z)(function(){var te,Et,fn=S.columnsState||{},St=fn.persistenceType,Mt=fn.persistenceKey;if(Mt&&St&&typeof window!="undefined"){var lt=window[St];try{var vn=lt==null?void 0:lt.getItem(Mt);if(vn)return JSON.parse(vn)}catch(Ht){console.warn(Ht)}}return S.columnsStateMap||((te=S.columnsState)===null||te===void 0?void 0:te.value)||((Et=S.columnsState)===null||Et===void 0?void 0:Et.defaultValue)||ln},{value:((u=S.columnsState)===null||u===void 0?void 0:u.value)||S.columnsStateMap,onChange:((n=S.columnsState)===null||n===void 0?void 0:n.onChange)||S.onColumnsStateChange}),At=(0,C.Z)(Gt,2),Bn=At[0],It=At[1];(0,T.useLayoutEffect)(function(){var te=S.columnsState||{},Et=te.persistenceType,fn=te.persistenceKey;if(fn&&Et&&typeof window!="undefined"){var St=window[Et];try{var Mt=St==null?void 0:St.getItem(fn);It(Mt?JSON.parse(Mt):ln)}catch(lt){console.warn(lt)}}},[S.columnsState,ln,It]),(0,V.noteOnce)(!S.columnsStateMap,"columnsStateMap\u5DF2\u7ECF\u5E9F\u5F03\uFF0C\u8BF7\u4F7F\u7528 columnsState.value \u66FF\u6362"),(0,V.noteOnce)(!S.columnsStateMap,"columnsStateMap has been discarded, please use columnSstate.value replacement");var xn=(0,T.useCallback)(function(){var te=S.columnsState||{},Et=te.persistenceType,fn=te.persistenceKey;if(!(!fn||!Et||typeof window=="undefined")){var St=window[Et];try{St==null||St.removeItem(fn)}catch(Mt){console.warn(Mt)}}},[S.columnsState]);(0,T.useEffect)(function(){var te,Et;if(!(!(!((te=S.columnsState)===null||te===void 0)&&te.persistenceKey)||!(!((Et=S.columnsState)===null||Et===void 0)&&Et.persistenceType))&&typeof window!="undefined"){var fn=S.columnsState,St=fn.persistenceType,Mt=fn.persistenceKey,lt=window[St];try{lt==null||lt.setItem(Mt,JSON.stringify(Bn))}catch(vn){console.warn(vn),xn()}}},[(p=S.columnsState)===null||p===void 0?void 0:p.persistenceKey,Bn,(c=S.columnsState)===null||c===void 0?void 0:c.persistenceType]);var nn={action:K.current,setAction:function(Et){K.current=Et},sortKeyColumns:de.current,setSortKeyColumns:function(Et){de.current=Et},propsRef:Q,columnsMap:Bn,keyWords:st,setKeyWords:function(Et){return ie(Et)},setTableSize:Tt,tableSize:re,prefixName:W.current,setPrefixName:function(Et){W.current=Et},setColumnsMap:It,columns:S.columns,rootDomRef:ze,clearPersistenceStorage:xn,defaultColumnKeyMap:ln};return Object.defineProperty(nn,"prefixName",{get:function(){return W.current}}),Object.defineProperty(nn,"sortKeyColumns",{get:function(){return de.current}}),Object.defineProperty(nn,"action",{get:function(){return K.current}}),nn}var qt=k(vt),Sn=qt,bn={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M859.9 168H164.1c-4.5 0-8.1 3.6-8.1 8v60c0 4.4 3.6 8 8.1 8h695.8c4.5 0 8.1-3.6 8.1-8v-60c0-4.4-3.6-8-8.1-8zM518.3 355a8 8 0 00-12.6 0l-112 141.7a7.98 7.98 0 006.3 12.9h73.9V848c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V509.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 355z"}}]},name:"vertical-align-top",theme:"outlined"},Un=bn,Dn=function(n,p){return T.createElement(ce.Z,(0,t.Z)((0,t.Z)({},n),{},{ref:p,icon:Un}))};Dn.displayName="VerticalAlignTopOutlined";var $e=T.forwardRef(Dn),Re={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M859.9 474H164.1c-4.5 0-8.1 3.6-8.1 8v60c0 4.4 3.6 8 8.1 8h695.8c4.5 0 8.1-3.6 8.1-8v-60c0-4.4-3.6-8-8.1-8zm-353.6-74.7c2.9 3.7 8.5 3.7 11.3 0l100.8-127.5c3.7-4.7.4-11.7-5.7-11.7H550V104c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v156h-62.8c-6 0-9.4 7-5.7 11.7l100.8 127.6zm11.4 225.4a7.14 7.14 0 00-11.3 0L405.6 752.3a7.23 7.23 0 005.7 11.7H474v156c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V764h62.8c6 0 9.4-7 5.7-11.7L517.7 624.7z"}}]},name:"vertical-align-middle",theme:"outlined"},De=Re,Je=function(n,p){return T.createElement(ce.Z,(0,t.Z)((0,t.Z)({},n),{},{ref:p,icon:De}))};Je.displayName="VerticalAlignMiddleOutlined";var mt=T.forwardRef(Je),Ut=e(66017),Vt=e(48171),Ze=e(22270),xe=e(31673),he=e(55241),oe=e(32808),Dt=function(n){var p,c,S,K;return K={},(0,N.Z)(K,n.componentCls,{width:"auto","&-title":{display:"flex",alignItems:"center",justifyContent:"space-between",height:"32px"},"&-overlay":(c={"*":{fontFamily:n.fontFamily,boxSizing:"border-box"}},(0,N.Z)(c,"".concat(n.antCls,"-popover-inner-content"),{width:"200px",paddingBlock:0,paddingInline:0,paddingBlockEnd:8}),(0,N.Z)(c,"".concat(n.antCls,"-tree-node-content-wrapper:hover"),{backgroundColor:"transparent"}),(0,N.Z)(c,"".concat(n.antCls,"-tree-draggable-icon"),{cursor:"grab"}),(0,N.Z)(c,"".concat(n.antCls,"-tree-treenode"),(p={alignItems:"center","&:hover":(0,N.Z)({},"".concat(n.componentCls,"-list-item-option"),{display:"block"})},(0,N.Z)(p,"".concat(n.antCls,"-tree-checkbox"),{marginInlineEnd:"4px"}),(0,N.Z)(p,"".concat(n.antCls,"-tree-title"),{width:"100%"}),p)),c)}),(0,N.Z)(K,"".concat(n.componentCls,"-list"),(S={display:"flex",flexDirection:"column",width:"100%",paddingBlockStart:8},(0,N.Z)(S,"&".concat(n.componentCls,"-list-group"),{paddingBlockStart:0}),(0,N.Z)(S,"&-title",{marginBlockStart:"6px",marginBlockEnd:"6px",paddingInlineStart:"24px",color:n.colorTextSecondary,fontSize:"12px"}),(0,N.Z)(S,"&-item",{display:"flex",alignItems:"center","&-title":{flex:1},"&-option":{display:"none",float:"right",cursor:"pointer","> span":{"> span.anticon":{color:n.colorPrimary}},"> span + span":{marginInlineStart:8}}}),S)),K};function Kn(u){return(0,Ee.Xj)("ColumnSetting",function(n){var p=(0,t.Z)((0,t.Z)({},n),{},{componentCls:".".concat(u)});return[Dt(p)]})}var b=["key","dataIndex","children"],d=function(n){var p=n.title,c=n.show,S=n.children,K=n.columnKey,ze=n.fixed,W=Sn.useContainer(),Q=W.columnsMap,ae=W.setColumnsMap;return c?(0,h.jsx)(z.Z,{title:p,children:(0,h.jsx)("span",{onClick:function(st){st.stopPropagation(),st.preventDefault();var ie=Q[K]||{},de=typeof ie.disable=="boolean"&&ie.disable;if(!de){var Fe=(0,t.Z)((0,t.Z)({},Q),{},(0,N.Z)({},K,(0,t.Z)((0,t.Z)({},ie),{},{fixed:ze})));ae(Fe)}},children:S})}):null},o=function(n){var p=n.columnKey,c=n.isLeaf,S=n.title,K=n.className,ze=n.fixed,W=(0,L.YB)(),Q=(0,T.useContext)(L.L_),ae=Q.hashId,nt=(0,h.jsxs)("span",{className:"".concat(K,"-list-item-option ").concat(ae),children:[(0,h.jsx)(d,{columnKey:p,fixed:"left",title:W.getMessage("tableToolBar.leftPin","\u56FA\u5B9A\u5728\u5217\u9996"),show:ze!=="left",children:(0,h.jsx)($e,{})}),(0,h.jsx)(d,{columnKey:p,fixed:void 0,title:W.getMessage("tableToolBar.noPin","\u4E0D\u56FA\u5B9A"),show:!!ze,children:(0,h.jsx)(mt,{})}),(0,h.jsx)(d,{columnKey:p,fixed:"right",title:W.getMessage("tableToolBar.rightPin","\u56FA\u5B9A\u5728\u5217\u5C3E"),show:ze!=="right",children:(0,h.jsx)(Ut.Z,{})})]});return(0,h.jsxs)("span",{className:"".concat(K,"-list-item ").concat(ae),children:[(0,h.jsx)("div",{className:"".concat(K,"-list-item-title ").concat(ae),children:S}),c?null:nt]},p)},f=function(n){var p,c,S=n.list,K=n.draggable,ze=n.checkable,W=n.className,Q=n.showTitle,ae=Q===void 0?!0:Q,nt=n.title,st=n.listHeight,ie=st===void 0?280:st,de=(0,T.useContext)(L.L_),Fe=de.hashId,Ft=Sn.useContainer(),re=Ft.columnsMap,Tt=Ft.setColumnsMap,ln=Ft.sortKeyColumns,Gt=Ft.setSortKeyColumns,At=S&&S.length>0,Bn=(0,T.useMemo)(function(){if(!At)return{};var te=[],Et=new Map,fn=function St(Mt,lt){return Mt.map(function(vn){var Ht,Pe=vn.key,rn=vn.dataIndex,jn=vn.children,sr=(0,j.Z)(vn,b),ur=Wt(Pe,[lt==null?void 0:lt.columnKey,sr.index].filter(Boolean).join("-")),_n=re[ur||"null"]||{show:!0};_n.show!==!1&&!jn&&te.push(ur);var Gn=(0,t.Z)((0,t.Z)({key:ur},(0,Kt.Z)(sr,["className"])),{},{selectable:!1,disabled:_n.disable===!0,disableCheckbox:typeof _n.disable=="boolean"?_n.disable:(Ht=_n.disable)===null||Ht===void 0?void 0:Ht.checkbox,isLeaf:lt?!0:void 0});if(jn){var Hn;Gn.children=St(jn,(0,t.Z)((0,t.Z)({},_n),{},{columnKey:ur})),!((Hn=Gn.children)===null||Hn===void 0)&&Hn.every(function(gn){return te==null?void 0:te.includes(gn.key)})&&te.push(ur)}return Et.set(Pe,Gn),Gn})};return{list:fn(S),keys:te,map:Et}},[re,S,At]),It=(0,Vt.J)(function(te,Et,fn){var St=(0,t.Z)({},re),Mt=(0,$.Z)(ln),lt=Mt.findIndex(function(rn){return rn===te}),vn=Mt.findIndex(function(rn){return rn===Et}),Ht=fn>lt;if(!(lt<0)){var Pe=Mt[lt];Mt.splice(lt,1),fn===0?Mt.unshift(Pe):Mt.splice(Ht?vn:vn+1,0,Pe),Mt.forEach(function(rn,jn){St[rn]=(0,t.Z)((0,t.Z)({},St[rn]||{}),{},{order:jn})}),Tt(St),Gt(Mt)}}),xn=(0,Vt.J)(function(te){var Et=(0,t.Z)({},re),fn=function St(Mt){var lt,vn,Ht=(0,t.Z)({},Et[Mt]);if(Ht.show=te.checked,!((lt=Bn.map)===null||lt===void 0||(vn=lt.get(Mt))===null||vn===void 0)&&vn.children){var Pe,rn;(Pe=Bn.map.get(Mt))===null||Pe===void 0||(rn=Pe.children)===null||rn===void 0||rn.forEach(function(jn){return St(jn.key)})}Et[Mt]=Ht};fn(te.node.key),Tt((0,t.Z)({},Et))});if(!At)return null;var nn=(0,h.jsx)(xe.Z,{itemHeight:24,draggable:K&&!!(!((p=Bn.list)===null||p===void 0)&&p.length)&&((c=Bn.list)===null||c===void 0?void 0:c.length)>1,checkable:ze,onDrop:function(Et){var fn=Et.node.key,St=Et.dragNode.key,Mt=Et.dropPosition,lt=Et.dropToGap,vn=Mt===-1||!lt?Mt+1:Mt;It(St,fn,vn)},blockNode:!0,onCheck:function(Et,fn){return xn(fn)},checkedKeys:Bn.keys,showLine:!1,titleRender:function(Et){var fn=(0,t.Z)((0,t.Z)({},Et),{},{children:void 0});return fn.title?(0,h.jsx)(o,(0,t.Z)((0,t.Z)({className:W},fn),{},{title:(0,Ze.h)(fn.title,fn),columnKey:fn.key})):null},height:ie,treeData:Bn.list});return(0,h.jsxs)(h.Fragment,{children:[ae&&(0,h.jsx)("span",{className:"".concat(W,"-list-title ").concat(Fe),children:nt}),nn]})},v=function(n){var p=n.localColumns,c=n.className,S=n.draggable,K=n.checkable,ze=n.listsHeight,W=(0,T.useContext)(L.L_),Q=W.hashId,ae=[],nt=[],st=[],ie=(0,L.YB)();p.forEach(function(Ft){if(!Ft.hideInSetting){var re=Ft.fixed;if(re==="left"){nt.push(Ft);return}if(re==="right"){ae.push(Ft);return}st.push(Ft)}});var de=ae&&ae.length>0,Fe=nt&&nt.length>0;return(0,h.jsxs)("div",{className:M()("".concat(c,"-list"),Q,(0,N.Z)({},"".concat(c,"-list-group"),de||Fe)),children:[(0,h.jsx)(f,{title:ie.getMessage("tableToolBar.leftFixedTitle","\u56FA\u5B9A\u5728\u5DE6\u4FA7"),list:nt,draggable:S,checkable:K,className:c,listHeight:ze}),(0,h.jsx)(f,{list:st,draggable:S,checkable:K,title:ie.getMessage("tableToolBar.noFixedTitle","\u4E0D\u56FA\u5B9A"),showTitle:Fe||de,className:c,listHeight:ze}),(0,h.jsx)(f,{title:ie.getMessage("tableToolBar.rightFixedTitle","\u56FA\u5B9A\u5728\u53F3\u4FA7"),list:ae,draggable:S,checkable:K,className:c,listHeight:ze})]})};function g(u){var n,p,c=(0,T.useRef)(null),S=Sn.useContainer(),K=u.columns,ze=u.checkedReset,W=ze===void 0?!0:ze,Q=S.columnsMap,ae=S.setColumnsMap,nt=S.clearPersistenceStorage;(0,T.useEffect)(function(){var xn,nn;if(!((xn=S.propsRef.current)===null||xn===void 0||(nn=xn.columnsState)===null||nn===void 0)&&nn.value){var te,Et;c.current=JSON.parse(JSON.stringify(((te=S.propsRef.current)===null||te===void 0||(Et=te.columnsState)===null||Et===void 0?void 0:Et.value)||{}))}},[]);var st=(0,Vt.J)(function(){var xn=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!0,nn={},te=function Et(fn){fn.forEach(function(St){var Mt=St.key,lt=St.fixed,vn=St.index,Ht=St.children,Pe=Wt(Mt,vn);Pe&&(nn[Pe]={show:xn,fixed:lt}),Ht&&Et(Ht)})};te(K),ae(nn)}),ie=(0,Vt.J)(function(xn){xn.target.checked?st():st(!1)}),de=(0,Vt.J)(function(){var xn,nn;nt==null||nt(),ae(c.current||((xn=S.propsRef.current)===null||xn===void 0||(nn=xn.columnsState)===null||nn===void 0?void 0:nn.defaultValue)||S.defaultColumnKeyMap)}),Fe=Object.values(Q).filter(function(xn){return!xn||xn.show===!1}),Ft=Fe.length>0&&Fe.length!==K.length,re=(0,L.YB)(),Tt=(0,T.useContext)(we.ZP.ConfigContext),ln=Tt.getPrefixCls,Gt=ln("pro-table-column-setting"),At=Kn(Gt),Bn=At.wrapSSR,It=At.hashId;return Bn((0,h.jsx)(he.Z,{arrowPointAtCenter:!0,title:(0,h.jsxs)("div",{className:"".concat(Gt,"-title ").concat(It),children:[(0,h.jsx)(oe.Z,{indeterminate:Ft,checked:Fe.length===0&&Fe.length!==K.length,onChange:function(nn){ie(nn)},children:re.getMessage("tableToolBar.columnDisplay","\u5217\u5C55\u793A")}),W?(0,h.jsx)("a",{onClick:de,className:"".concat(Gt,"-action-rest-button"),children:re.getMessage("tableToolBar.reset","\u91CD\u7F6E")}):null,u!=null&&u.extra?(0,h.jsx)(ue.Z,{size:12,align:"center",children:u.extra}):null]}),overlayClassName:"".concat(Gt,"-overlay ").concat(It),trigger:"click",placement:"bottomRight",content:(0,h.jsx)(v,{checkable:(n=u.checkable)!==null&&n!==void 0?n:!0,draggable:(p=u.draggable)!==null&&p!==void 0?p:!0,className:Gt,localColumns:K,listsHeight:u.listsHeight}),children:u.children||(0,h.jsx)(z.Z,{title:re.getMessage("tableToolBar.columnSetting","\u5217\u8BBE\u7F6E"),children:(0,h.jsx)(Ot.Z,{})})}))}var D=g,P=e(81736),U=e(67875),fe=e(69677),X=e(59209),w=e(80882),l=e(86413),G=e(13013),ne=function(n){var p=(0,T.useContext)(L.L_),c=p.hashId,S=n.items,K=S===void 0?[]:S,ze=n.type,W=ze===void 0?"inline":ze,Q=n.prefixCls,ae=n.activeKey,nt=(0,je.Z)(ae,{value:ae,onChange:n.onChange}),st=(0,C.Z)(nt,2),ie=st[0],de=st[1];if(K.length<1)return null;var Fe=K.find(function(re){return re.key===ie})||K[0];if(W==="inline")return(0,h.jsx)("div",{className:M()("".concat(Q,"-menu"),"".concat(Q,"-inline-menu"),c),children:K.map(function(re,Tt){return(0,h.jsx)("div",{onClick:function(){de(re.key)},className:M()("".concat(Q,"-inline-menu-item"),Fe.key===re.key?"".concat(Q,"-inline-menu-item-active"):void 0,c),children:re.label},re.key||Tt)})});if(W==="tab")return(0,h.jsx)(U.Z,{items:K.map(function(re){var Tt;return(0,t.Z)((0,t.Z)({},re),{},{key:(Tt=re.key)===null||Tt===void 0?void 0:Tt.toString()})}),activeKey:Fe.key,onTabClick:function(Tt){return de(Tt)},children:K==null?void 0:K.map(function(re,Tt){return(0,T.createElement)(U.Z.TabPane,(0,t.Z)((0,t.Z)({},re),{},{key:re.key||Tt,tab:re.label}))})});var Ft=(0,l.Q)({selectedKeys:[Fe.key],onClick:function(Tt){de(Tt.key)},items:K.map(function(re,Tt){return{key:re.key||Tt,disabled:re.disabled,label:re.label}})});return(0,h.jsx)("div",{className:M()("".concat(Q,"-menu"),"".concat(Q,"-dropdownmenu")),children:(0,h.jsx)(G.Z,(0,t.Z)((0,t.Z)({trigger:["click"]},Ft),{},{children:(0,h.jsxs)(ue.Z,{className:"".concat(Q,"-dropdownmenu-label"),children:[Fe.label,(0,h.jsx)(w.Z,{})]})}))})},q=ne,Y=function(n){var p;return(0,N.Z)({},n.componentCls,(p={lineHeight:"1","&-container":{display:"flex",justifyContent:"space-between",paddingBlock:n.padding,paddingInline:0,"&-mobile":{flexDirection:"column"}},"&-title":{display:"flex",alignItems:"center",justifyContent:"flex-start",color:n.colorTextHeading,fontWeight:"500",fontSize:n.fontSizeLG},"&-search:not(:last-child)":{display:"flex",alignItems:"center",justifyContent:"flex-start"},"&-setting-item":{marginBlock:0,marginInline:4,color:n.colorIconHover,fontSize:n.fontSizeLG,cursor:"pointer","> span":{display:"block",width:"100%",height:"100%"},"&:hover":{color:n.colorPrimary}},"&-left":{display:"flex",alignItems:"center",justifyContent:"flex-start"},"&-right":{display:"flex",justifyContent:"flex-end"},"&-extra-line":{marginBlockEnd:n.margin},"&-filter":(0,N.Z)({"&:not(:last-child)":{marginInlineEnd:n.margin},display:"flex",alignItems:"center"},"div.$".concat(n.antCls,"-pro-table-search"),{marginBlock:0,marginInline:0,paddingBlock:0,paddingInline:0}),"&-inline-menu-item":{display:"inline-block",marginInlineEnd:n.marginLG,cursor:"pointer",opacity:"0.75","&-active":{fontWeight:"bold",opacity:"1"}}},(0,N.Z)(p,"".concat(n.antCls,"-tabs-top > ").concat(n.antCls,"-tabs-nav"),(0,N.Z)({marginBlockEnd:0,"&::before":{borderBlockEnd:0}},"".concat(n.antCls,"-tabs-nav-list"),{marginBlockStart:0,"${token.antCls}-tabs-tab":{paddingBlockStart:0}})),(0,N.Z)(p,"&-dropdownmenu-label",{fontWeight:"bold",fontSize:n.fontSizeIcon,textAlign:"center",cursor:"pointer"}),(0,N.Z)(p,"@media (max-width: 575px)",(0,N.Z)({},n.componentCls,{"&-container":{display:"flex",flexWrap:"wrap"},"&-left":{marginBlockEnd:"16px"}})),p))};function ot(u){return(0,Ee.Xj)("ProTableListToolBar",function(n){var p=(0,t.Z)((0,t.Z)({},n),{},{componentCls:".".concat(u)});return[Y(p)]})}function at(u){if(T.isValidElement(u))return u;if(u){var n=u,p=n.icon,c=n.tooltip,S=n.onClick,K=n.key;return p&&c?(0,h.jsx)(z.Z,{title:c,children:(0,h.jsx)("span",{onClick:function(){S&&S(K)},children:p},K)}):p}return null}var ct=function(n){var p,c=n.prefixCls,S=n.tabs,K=S===void 0?{}:S,ze=n.multipleLine,W=n.filtersNode;return ze?(0,h.jsx)("div",{className:"".concat(c,"-extra-line"),children:K.items&&K.items.length?(0,h.jsx)(U.Z,{activeKey:K.activeKey,items:K.items.map(function(Q,ae){var nt;return(0,t.Z)((0,t.Z)({label:Q.tab},Q),{},{key:((nt=Q.key)===null||nt===void 0?void 0:nt.toString())||(ae==null?void 0:ae.toString())})}),onChange:K.onChange,tabBarExtraContent:W,children:(p=K.items)===null||p===void 0?void 0:p.map(function(Q,ae){return(0,T.createElement)(U.Z.TabPane,(0,t.Z)((0,t.Z)({},Q),{},{key:Q.key||ae,tab:Q.tab}))})}):W}):null},Qe=function(n){var p=n.prefixCls,c=n.title,S=n.subTitle,K=n.tooltip,ze=n.className,W=n.style,Q=n.search,ae=n.onSearch,nt=n.multipleLine,st=nt===void 0?!1:nt,ie=n.filter,de=n.actions,Fe=de===void 0?[]:de,Ft=n.settings,re=Ft===void 0?[]:Ft,Tt=n.tabs,ln=Tt===void 0?{}:Tt,Gt=n.menu,At=(0,T.useContext)(we.ZP.ConfigContext),Bn=At.getPrefixCls,It=Bn("pro-table-list-toolbar",p),xn=ot(It),nn=xn.wrapSSR,te=xn.hashId,Et=(0,L.YB)(),fn=(0,X.ZP)(),St=fn==="sm"||fn==="xs",Mt=Et.getMessage("tableForm.inputPlaceholder","\u8BF7\u8F93\u5165"),lt=(0,T.useMemo)(function(){return Q?T.isValidElement(Q)?Q:(0,h.jsx)(fe.Z.Search,(0,t.Z)((0,t.Z)({style:{width:200},placeholder:Mt},Q),{},{onSearch:function(){for(var Hn,gn=arguments.length,Vn=new Array(gn),kn=0;kna":{fontSize:n.fontSize}}),(0,N.Z)(S,"".concat(n.antCls,"-table").concat(n.antCls,"-table-tbody").concat(n.antCls,"-table-wrapper:only-child").concat(n.antCls,"-table"),{marginBlock:0,marginInline:0}),(0,N.Z)(S,"".concat(n.antCls,"-table").concat(n.antCls,"-table-middle ").concat(n.componentCls),{marginBlock:0,marginInline:-8}),(0,N.Z)(S,"& &-search",(c={marginBlockEnd:"16px",background:n.colorBgContainer,"&-ghost":{background:"transparent"}},(0,N.Z)(c,"&".concat(n.componentCls,"-form"),{marginBlock:0,marginInline:0,paddingBlock:0,paddingInline:16,overflow:"unset"}),(0,N.Z)(c,"&-light-filter",{marginBlockEnd:0,paddingBlock:0,paddingInline:0}),(0,N.Z)(c,"&-form-option",(p={},(0,N.Z)(p,"".concat(n.antCls,"-form-item"),{}),(0,N.Z)(p,"".concat(n.antCls,"-form-item-label"),{}),(0,N.Z)(p,"".concat(n.antCls,"-form-item-control-input"),{}),p)),(0,N.Z)(c,"@media (max-width: 575px)",(0,N.Z)({},n.componentCls,(0,N.Z)({height:"auto !important",paddingBlockEnd:"24px"},"".concat(n.antCls,"-form-item-label"),{minWidth:"80px",textAlign:"start"}))),c)),(0,N.Z)(S,"&-toolbar",{display:"flex",alignItems:"center",justifyContent:"space-between",height:"64px",paddingInline:24,paddingBlock:0,"&-option":{display:"flex",alignItems:"center",justifyContent:"flex-end"},"&-title":{flex:"1",color:n.colorTextLabel,fontWeight:"500",fontSize:"16px",lineHeight:"24px",opacity:"0.85"}}),S)),(0,N.Z)(K,"@keyframes turn",{"0%":{transform:"rotate(0deg)"},"25%":{transform:"rotate(90deg)"},"50%":{transform:"rotate(180deg)"},"75%":{transform:"rotate(270deg)"},"100%":{transform:"rotate(360deg)"}}),(0,N.Z)(K,"@media (max-width: ".concat(n.screenXS,")"),(0,N.Z)({},n.componentCls,(0,N.Z)({},"".concat(n.antCls,"-table"),{width:"100%",overflowX:"auto","&-thead > tr,&-tbody > tr":{"> th,> td":{whiteSpace:"pre",">span":{display:"block"}}}}))),(0,N.Z)(K,"@media (max-width: 575px)",(0,N.Z)({},"".concat(n.componentCls,"-toolbar"),{flexDirection:"column",alignItems:"flex-start",justifyContent:"flex-start",height:"auto",marginBlockEnd:"16px",marginInlineStart:"16px",paddingBlock:8,paddingInline:8,paddingBlockStart:"16px",lineHeight:"normal","&-title":{marginBlockEnd:16},"&-option":{display:"flex",justifyContent:"space-between",width:"100%"},"&-default-option":{display:"flex",flex:"1",alignItems:"center",justifyContent:"flex-end"}})),K};function Xn(u){return(0,Ee.Xj)("ProTable",function(n){var p=(0,t.Z)((0,t.Z)({},n),{},{componentCls:".".concat(u)});return[tn(p)]})}var In=e(26369),zn=e(10178),Qn=["data","success","total"],qn=function(n){var p=n.pageInfo;if(p){var c=p.current,S=p.defaultCurrent,K=p.pageSize,ze=p.defaultPageSize;return{current:c||S||1,total:0,pageSize:K||ze||20}}return{current:1,total:0,pageSize:20}},m=function(n,p,c){var S=(0,T.useRef)(!1),K=c||{},ze=K.onLoad,W=K.manual,Q=K.polling,ae=K.onRequestError,nt=K.debounceTime,st=nt===void 0?20:nt,ie=(0,T.useRef)(W),de=(0,T.useRef)(),Fe=(0,je.Z)(p,{value:c==null?void 0:c.dataSource,onChange:c==null?void 0:c.onDataSourceChange}),Ft=(0,C.Z)(Fe,2),re=Ft[0],Tt=Ft[1],ln=(0,je.Z)(!1,{value:c==null?void 0:c.loading,onChange:c==null?void 0:c.onLoadingChange}),Gt=(0,C.Z)(ln,2),At=Gt[0],Bn=Gt[1],It=(0,T.useRef)(!1),xn=(0,je.Z)(function(){return qn(c)},{onChange:c==null?void 0:c.onPageInfoChange}),nn=(0,C.Z)(xn,2),te=nn[0],Et=nn[1],fn=(0,Vt.J)(function(Vn){(Vn.current!==te.current||Vn.pageSize!==te.pageSize||Vn.total!==te.total)&&Et(Vn)}),St=(0,je.Z)(!1),Mt=(0,C.Z)(St,2),lt=Mt[0],vn=Mt[1],Ht=function(kn,ar){Tt(kn),(te==null?void 0:te.total)!==ar&&fn((0,t.Z)((0,t.Z)({},te),{},{total:ar||kn.length}))},Pe=(0,In.D)(te==null?void 0:te.current),rn=(0,In.D)(te==null?void 0:te.pageSize),jn=(0,In.D)(Q),sr=c||{},ur=sr.effects,_n=ur===void 0?[]:ur,Gn=(0,Vt.J)(function(){(0,F.Z)(At)==="object"?Bn((0,t.Z)((0,t.Z)({},At),{},{spinning:!1})):Bn(!1),vn(!1)}),Hn=function(){var Vn=(0,a.Z)((0,r.Z)().mark(function kn(ar){var mr,cr,pr,cn,kt,nr,fr,xr,gr,Sr,Zr,yr;return(0,r.Z)().wrap(function(rr){for(;;)switch(rr.prev=rr.next){case 0:if(!(At&&typeof At=="boolean"||It.current||!n)){rr.next=2;break}return rr.abrupt("return",[]);case 2:if(!ie.current){rr.next=5;break}return ie.current=!1,rr.abrupt("return",[]);case 5:return ar?vn(!0):(0,F.Z)(At)==="object"?Bn((0,t.Z)((0,t.Z)({},At),{},{spinning:!0})):Bn(!0),It.current=!0,mr=te||{},cr=mr.pageSize,pr=mr.current,rr.prev=8,cn=(c==null?void 0:c.pageInfo)!==!1?{current:pr,pageSize:cr}:void 0,rr.next=12,n(cn);case 12:if(rr.t0=rr.sent,rr.t0){rr.next=15;break}rr.t0={};case 15:if(kt=rr.t0,nr=kt.data,fr=nr===void 0?[]:nr,xr=kt.success,gr=kt.total,Sr=gr===void 0?0:gr,Zr=(0,j.Z)(kt,Qn),xr!==!1){rr.next=24;break}return rr.abrupt("return",[]);case 24:return yr=pt(fr,[c.postData].filter(function(Rr){return Rr})),Ht(yr,Sr),ze==null||ze(yr,Zr),rr.abrupt("return",yr);case 30:if(rr.prev=30,rr.t1=rr.catch(8),ae!==void 0){rr.next=34;break}throw new Error(rr.t1);case 34:re===void 0&&Tt([]),ae(rr.t1);case 36:return rr.prev=36,It.current=!1,Gn(),rr.finish(36);case 40:return rr.abrupt("return",[]);case 41:case"end":return rr.stop()}},kn,null,[[8,30,36,40]])}));return function(ar){return Vn.apply(this,arguments)}}(),gn=(0,zn.D)(function(){var Vn=(0,a.Z)((0,r.Z)().mark(function kn(ar){var mr,cr;return(0,r.Z)().wrap(function(cn){for(;;)switch(cn.prev=cn.next){case 0:return de.current&&clearTimeout(de.current),cn.next=3,Hn(ar);case 3:return mr=cn.sent,cr=(0,Ze.h)(Q,mr),cr&&!S.current&&(de.current=setTimeout(function(){gn.run(cr)},Math.max(cr,2e3))),cn.abrupt("return",mr);case 7:case"end":return cn.stop()}},kn)}));return function(kn){return Vn.apply(this,arguments)}}(),st||10);return(0,T.useEffect)(function(){return Q||clearTimeout(de.current),!jn&&Q&&gn.run(!0),function(){clearTimeout(de.current)}},[Q]),(0,T.useLayoutEffect)(function(){return S.current=!1,function(){S.current=!0}},[]),(0,T.useEffect)(function(){var Vn=te||{},kn=Vn.current,ar=Vn.pageSize;(!Pe||Pe===kn)&&(!rn||rn===ar)||c.pageInfo&&re&&(re==null?void 0:re.length)>ar||kn!==void 0&&re&&re.length<=ar&&gn.run(!1)},[te==null?void 0:te.current]),(0,T.useEffect)(function(){!rn||gn.run(!1)},[te==null?void 0:te.pageSize]),(0,B.KW)(function(){return gn.run(!1),W||(ie.current=!1),function(){gn.cancel()}},[].concat((0,$.Z)(_n),[W])),{dataSource:re,setDataSource:Tt,loading:At,reload:function(){var Vn=(0,a.Z)((0,r.Z)().mark(function ar(){return(0,r.Z)().wrap(function(cr){for(;;)switch(cr.prev=cr.next){case 0:return cr.next=2,gn.run(!1);case 2:case"end":return cr.stop()}},ar)}));function kn(){return Vn.apply(this,arguments)}return kn}(),pageInfo:te,pollingLoading:lt,reset:function(){var Vn=(0,a.Z)((0,r.Z)().mark(function ar(){var mr,cr,pr,cn,kt,nr,fr,xr;return(0,r.Z)().wrap(function(Sr){for(;;)switch(Sr.prev=Sr.next){case 0:mr=c||{},cr=mr.pageInfo,pr=cr||{},cn=pr.defaultCurrent,kt=cn===void 0?1:cn,nr=pr.defaultPageSize,fr=nr===void 0?20:nr,xr={current:kt,total:0,pageSize:fr},fn(xr);case 4:case"end":return Sr.stop()}},ar)}));function kn(){return Vn.apply(this,arguments)}return kn}(),setPageInfo:function(){var Vn=(0,a.Z)((0,r.Z)().mark(function ar(mr){return(0,r.Z)().wrap(function(pr){for(;;)switch(pr.prev=pr.next){case 0:fn((0,t.Z)((0,t.Z)({},te),mr));case 1:case"end":return pr.stop()}},ar)}));function kn(ar){return Vn.apply(this,arguments)}return kn}()}},s=m,R=function(n){return function(p,c){var S,K,ze=p.fixed,W=p.index,Q=c.fixed,ae=c.index;if(ze==="left"&&Q!=="left"||Q==="right"&&ze!=="right")return-2;if(Q==="left"&&ze!=="left"||ze==="right"&&Q!=="right")return 2;var nt=p.key||"".concat(W),st=c.key||"".concat(ae);if(((S=n[nt])===null||S===void 0?void 0:S.order)||((K=n[st])===null||K===void 0?void 0:K.order)){var ie,de;return(((ie=n[nt])===null||ie===void 0?void 0:ie.order)||0)-(((de=n[st])===null||de===void 0?void 0:de.order)||0)}return(p.index||0)-(c.index||0)}},y=e(99108),I=e(28700),E=function(n){var p={};return Object.keys(n||{}).forEach(function(c){var S;Array.isArray(n[c])&&((S=n[c])===null||S===void 0?void 0:S.length)===0||n[c]!==void 0&&(p[c]=n[c])}),p},se=e(77398),J=e(74763),_=e(94787),Ct=e(66758),jt=e(97269),rt=e(7772),Yt=e(97462),sn=e(90081),wn=e(2026),En=["children"],dn=["",null,void 0],On=function(){for(var n=arguments.length,p=new Array(n),c=0;ccr.length?(cr.push(Vn),cr):(cr.splice((W==null?void 0:W.current)*(W==null?void 0:W.pageSize)-1,0,Vn),cr)}return[].concat((0,$.Z)(S.dataSource),[Vn])},vn=function(){return(0,t.Z)((0,t.Z)({},Et),{},{size:ae,rowSelection:Q===!1?void 0:Q,className:p,style:st,columns:St.map(function(Gn){return Gn.isExtraColumns?Gn.extraColumn:Gn}),loading:S.loading,dataSource:nn.newLineRecord?lt(S.dataSource):S.dataSource,pagination:W,onChange:function(Hn,gn,Vn,kn){var ar;if((ar=Et.onChange)===null||ar===void 0||ar.call(Et,Hn,gn,Vn,kn),Mt||Gt((0,Ce.Y)(gn)),Array.isArray(Vn)){var mr=Vn.reduce(function(kt,nr){return(0,t.Z)((0,t.Z)({},kt),{},(0,N.Z)({},"".concat(nr.field),nr.order))},{});ln((0,Ce.Y)(mr))}else{var cr,pr=(cr=Vn.column)===null||cr===void 0?void 0:cr.sorter,cn=(pr==null?void 0:pr.toString())===pr;ln((0,Ce.Y)((0,N.Z)({},"".concat(cn?pr:Vn.field),Vn.order))||{})}}})},Ht=(0,h.jsx)(Se.Z,(0,t.Z)((0,t.Z)({},vn()),{},{rowKey:n})),Pe=u.tableViewRender?u.tableViewRender((0,t.Z)((0,t.Z)({},vn()),{},{rowSelection:Q!==!1?Q:void 0}),Ht):Ht,rn=(0,T.useMemo)(function(){if(u.editable&&!u.name){var _n,Gn,Hn,gn;return(0,h.jsxs)(h.Fragment,{children:[ie,re,(0,T.createElement)(pe.ZP,(0,t.Z)((0,t.Z)({},(_n=u.editable)===null||_n===void 0?void 0:_n.formProps),{},{formRef:(Gn=u.editable)===null||Gn===void 0||(Hn=Gn.formProps)===null||Hn===void 0?void 0:Hn.formRef,component:!1,form:(gn=u.editable)===null||gn===void 0?void 0:gn.form,onValuesChange:nn.onValuesChange,key:"table",submitter:!1,omitNil:!1,dateFormatter:u.dateFormatter}),Pe)]})}return(0,h.jsxs)(h.Fragment,{children:[ie,re,Pe]})},[re,u.loading,!!u.editable,Pe,ie]),jn=Ft===!1||!!u.name?rn:(0,h.jsx)(Z.ZP,(0,t.Z)((0,t.Z)({ghost:u.ghost,bordered:Zt("table",xn),bodyStyle:ie?{paddingBlockStart:0}:{padding:0}},Ft),{},{children:rn})),sr=function(){return u.tableRender?u.tableRender(u,jn,{toolbar:ie||void 0,alert:re||void 0,table:Pe||void 0}):jn},ur=(0,h.jsxs)("div",{className:M()(It,(0,N.Z)({},"".concat(c,"-polling"),S.pollingLoading)),style:Fe,ref:fn.rootDomRef,children:[Bn?null:de,ze!=="form"&&u.tableExtraRender&&(0,h.jsx)("div",{className:M()(It,"".concat(c,"-extra")),children:u.tableExtraRender(u,S.dataSource||[])}),ze!=="form"&&sr()]});return!At||!(At!=null&&At.fullScreen)?ur:(0,h.jsx)(we.ZP,{getPopupContainer:function(){return fn.rootDomRef.current||document.body},children:ur})}var Wn={},hn=function(n){var p,c=n.cardBordered,S=n.request,K=n.className,ze=n.params,W=ze===void 0?Wn:ze,Q=n.defaultData,ae=n.headerTitle,nt=n.postData,st=n.ghost,ie=n.pagination,de=n.actionRef,Fe=n.columns,Ft=Fe===void 0?[]:Fe,re=n.toolBarRender,Tt=n.onLoad,ln=n.onRequestError,Gt=n.style,At=n.cardProps,Bn=n.tableStyle,It=n.tableClassName,xn=n.columnsStateMap,nn=n.onColumnsStateChange,te=n.options,Et=n.search,fn=n.name,St=n.onLoadingChange,Mt=n.rowSelection,lt=Mt===void 0?!1:Mt,vn=n.beforeSearchSubmit,Ht=n.tableAlertRender,Pe=n.defaultClassName,rn=n.formRef,jn=n.type,sr=jn===void 0?"table":jn,ur=n.columnEmptyText,_n=ur===void 0?"-":ur,Gn=n.toolbar,Hn=n.rowKey,gn=n.manualRequest,Vn=n.polling,kn=n.tooltip,ar=n.revalidateOnFocus,mr=ar===void 0?!1:ar,cr=(0,j.Z)(n,Rn),pr=Xn(n.defaultClassName),cn=pr.wrapSSR,kt=pr.hashId,nr=M()(Pe,K,kt),fr=(0,T.useRef)(),xr=(0,T.useRef)(),gr=rn||xr;(0,T.useImperativeHandle)(de,function(){return fr.current});var Sr=(0,je.Z)(lt?(lt==null?void 0:lt.defaultSelectedRowKeys)||[]:void 0,{value:lt?lt.selectedRowKeys:void 0}),Zr=(0,C.Z)(Sr,2),yr=Zr[0],Ir=Zr[1],rr=(0,T.useRef)([]),Rr=(0,T.useCallback)(function(Jt,yn){Ir(Jt),(!lt||!(lt!=null&<.selectedRowKeys))&&(rr.current=yn)},[Ir]),Fr=(0,je.Z)(function(){if(!(gn||Et!==!1))return{}}),wr=(0,C.Z)(Fr,2),Mr=wr[0],Pr=wr[1],Lr=(0,je.Z)({}),Dr=(0,C.Z)(Lr,2),Br=Dr[0],Or=Dr[1],$r=(0,je.Z)({}),Kr=(0,C.Z)($r,2),Ar=Kr[0],zr=Kr[1];(0,T.useEffect)(function(){var Jt=Qt(Ft),yn=Jt.sort,er=Jt.filter;Or(er),zr(yn)},[]);var Hr=(0,L.YB)(),or=(0,F.Z)(ie)==="object"?ie:{defaultCurrent:1,defaultPageSize:20,pageSize:20,current:1},$n=Sn.useContainer(),Cr=(0,T.useMemo)(function(){if(!!S)return function(){var Jt=(0,a.Z)((0,r.Z)().mark(function yn(er){var vr,br;return(0,r.Z)().wrap(function(Er){for(;;)switch(Er.prev=Er.next){case 0:return vr=(0,t.Z)((0,t.Z)((0,t.Z)({},er||{}),Mr),W),delete vr._timestamp,Er.next=4,S(vr,Ar,Br);case 4:return br=Er.sent,Er.abrupt("return",br);case 6:case"end":return Er.stop()}},yn)}));return function(yn){return Jt.apply(this,arguments)}}()},[Mr,W,Br,Ar,S]),lr=s(Cr,Q,{pageInfo:ie===!1?!1:or,loading:n.loading,dataSource:n.dataSource,onDataSourceChange:n.onDataSourceChange,onLoad:Tt,onLoadingChange:St,onRequestError:ln,postData:nt,revalidateOnFocus:mr,manual:Mr===void 0,polling:Vn,effects:[(0,Ie.P)(W),(0,Ie.P)(Mr),(0,Ie.P)(Br),(0,Ie.P)(Ar)],debounceTime:n.debounceTime,onPageInfoChange:function(yn){var er,vr;sr==="list"||!ie||!Cr||(ie==null||(er=ie.onChange)===null||er===void 0||er.call(ie,yn.current,yn.pageSize),ie==null||(vr=ie.onShowSizeChange)===null||vr===void 0||vr.call(ie,yn.current,yn.pageSize))}});(0,T.useEffect)(function(){var Jt;if(!(n.manualRequest||!n.request||!mr||((Jt=n.form)===null||Jt===void 0?void 0:Jt.ignoreRules))){var yn=function(){document.visibilityState==="visible"&&lr.reload()};return document.addEventListener("visibilitychange",yn),function(){return document.removeEventListener("visibilitychange",yn)}}},[]);var hr=T.useRef(new Map),jr=T.useMemo(function(){return typeof Hn=="function"?Hn:function(Jt,yn){var er;return yn===-1?Jt==null?void 0:Jt[Hn]:n.name?yn==null?void 0:yn.toString():(er=Jt==null?void 0:Jt[Hn])!==null&&er!==void 0?er:yn==null?void 0:yn.toString()}},[n.name,Hn]);(0,T.useMemo)(function(){var Jt;if(!((Jt=lr.dataSource)===null||Jt===void 0)&&Jt.length){var yn=lr.dataSource.map(function(er){var vr=jr(er,-1);return hr.current.set(vr,er),vr});return yn}return[]},[lr.dataSource,jr]),(0,T.useEffect)(function(){rr.current=yr==null?void 0:yr.map(function(Jt){var yn;return(yn=hr.current)===null||yn===void 0?void 0:yn.get(Jt)})},[yr]);var Xr=(0,T.useMemo)(function(){var Jt=ie===!1?!1:(0,t.Z)({},ie),yn=(0,t.Z)((0,t.Z)({},lr.pageInfo),{},{setPageInfo:function(vr){var br=vr.pageSize,Tr=vr.current,Er=lr.pageInfo;if(br===Er.pageSize||Er.current===1){lr.setPageInfo({pageSize:br,current:Tr});return}S&&lr.setDataSource([]),lr.setPageInfo({pageSize:br,current:sr==="list"?Tr:1})}});return S&&Jt&&(delete Jt.onChange,delete Jt.onShowSizeChange),qe(Jt,yn,Hr)},[ie,lr,Hr]);(0,B.KW)(function(){var Jt;n.request&&W&&lr.dataSource&&(lr==null||(Jt=lr.pageInfo)===null||Jt===void 0?void 0:Jt.current)!==1&&lr.setPageInfo({current:1})},[W]),$n.setPrefixName(n.name);var Ur=(0,T.useCallback)(function(){lt&<.onChange&<.onChange([],[],{type:"none"}),Rr([],[])},[lt,Rr]);$n.setAction(fr.current),$n.propsRef.current=n;var Wr=(0,ye.CB)((0,t.Z)((0,t.Z)({},n.editable),{},{tableName:n.name,getRowKey:jr,childrenColumnName:((p=n.expandable)===null||p===void 0?void 0:p.childrenColumnName)||"children",dataSource:lr.dataSource||[],setDataSource:function(yn){var er,vr;(er=n.editable)===null||er===void 0||(vr=er.onValuesChange)===null||vr===void 0||vr.call(er,void 0,yn),lr.setDataSource(yn)}}));A(fr,lr,{fullScreen:function(){var yn;if(!(!(!((yn=$n.rootDomRef)===null||yn===void 0)&&yn.current)||!document.fullscreenEnabled))if(document.fullscreenElement)document.exitFullscreen();else{var er;(er=$n.rootDomRef)===null||er===void 0||er.current.requestFullscreen()}},onCleanSelected:function(){Ur()},resetAll:function(){var yn;Ur(),Or({}),zr({}),$n.setKeyWords(void 0),lr.setPageInfo({current:1}),gr==null||(yn=gr.current)===null||yn===void 0||yn.resetFields(),Pr({})},editableUtils:Wr}),de&&(de.current=fr.current);var Nr=(0,T.useMemo)(function(){var Jt;return O({columns:Ft,counter:$n,columnEmptyText:_n,type:sr,editableUtils:Wr,rowKey:Hn,childrenColumnName:(Jt=n.expandable)===null||Jt===void 0?void 0:Jt.childrenColumnName}).sort(R($n.columnsMap))},[Ft,$n==null?void 0:$n.sortKeyColumns,$n==null?void 0:$n.columnsMap,_n,sr,Wr.editableKeys&&Wr.editableKeys.join(",")]);(0,B.Au)(function(){if(Nr&&Nr.length>0){var Jt=Nr.map(function(yn){return Wt(yn.key,yn.index)});$n.setSortKeyColumns(Jt)}},[Nr],["render","renderFormItem"],100),(0,B.KW)(function(){var Jt=lr.pageInfo,yn=ie||{},er=yn.current,vr=er===void 0?Jt==null?void 0:Jt.current:er,br=yn.pageSize,Tr=br===void 0?Jt==null?void 0:Jt.pageSize:br;ie&&(vr||Tr)&&(Tr!==(Jt==null?void 0:Jt.pageSize)||vr!==(Jt==null?void 0:Jt.current))&&lr.setPageInfo({pageSize:Tr||Jt.pageSize,current:vr||Jt.current})},[ie&&ie.pageSize,ie&&ie.current]);var Yr=(0,t.Z)((0,t.Z)({selectedRowKeys:yr},lt),{},{onChange:function(yn,er,vr){lt&<.onChange&<.onChange(yn,er,vr),Rr(yn,er)}}),Vr=Et!==!1&&(Et==null?void 0:Et.filterType)==="light",Jr=function(yn){if(te&&te.search){var er,vr,br=te.search===!0?{}:te.search,Tr=br.name,Er=Tr===void 0?"keyword":Tr,qr=(er=te.search)===null||er===void 0||(vr=er.onSearch)===null||vr===void 0?void 0:vr.call(er,$n.keyWords);if(qr!==!1){Pr((0,t.Z)((0,t.Z)({},yn),{},(0,N.Z)({},Er,$n.keyWords)));return}}Pr(yn)},Qr=(0,T.useMemo)(function(){if((0,F.Z)(lr.loading)==="object"){var Jt;return((Jt=lr.loading)===null||Jt===void 0?void 0:Jt.spinning)||!1}return lr.loading},[lr.loading]),Gr=Et===!1&&sr!=="form"?null:(0,h.jsx)(gt,{pagination:Xr,beforeSearchSubmit:vn,action:fr,columns:Ft,onFormSearchSubmit:function(yn){Jr(yn)},ghost:st,onReset:n.onReset,onSubmit:n.onSubmit,loading:!!Qr,manualRequest:gn,search:Et,form:n.form,formRef:gr,type:n.type||"table",cardBordered:n.cardBordered,dateFormatter:n.dateFormatter}),_r=re===!1?null:(0,h.jsx)(Zn,{headerTitle:ae,hideToolbar:te===!1&&!ae&&!re&&!Gn&&!Vr,selectedRows:rr.current,selectedRowKeys:yr,tableColumn:Nr,tooltip:kn,toolbar:Gn,onFormSearchSubmit:function(yn){Pr((0,t.Z)((0,t.Z)({},Mr),yn))},searchNode:Vr?Gr:null,options:te,actionRef:fr,toolBarRender:re}),kr=lt!==!1?(0,h.jsx)(ge,{selectedRowKeys:yr,selectedRows:rr.current,onCleanSelected:Ur,alertOptionRender:cr.tableAlertOptionRender,alertInfoRender:Ht,alwaysShowAlert:lt==null?void 0:lt.alwaysShowAlert}):null;return cn((0,h.jsx)(pn,(0,t.Z)((0,t.Z)({},n),{},{name:fn,defaultClassName:Pe,size:$n.tableSize,onSizeChange:$n.setTableSize,pagination:Xr,searchNode:Gr,rowSelection:lt!==!1?Yr:void 0,className:nr,tableColumn:Nr,isLightFilter:Vr,action:lr,alertDom:kr,toolbarDom:_r,onSortChange:zr,onFilterChange:Or,editableUtils:Wr,getRowKey:jr})))},Ve=function(n){var p=(0,T.useContext)(we.ZP.ConfigContext),c=p.getPrefixCls,S=n.ErrorBoundary===!1?T.Fragment:n.ErrorBoundary||H.S;return(0,h.jsx)(Sn.Provider,{initialState:n,children:(0,h.jsx)(L._Y,{needDeps:!0,children:(0,h.jsx)(S,{children:(0,h.jsx)(hn,(0,t.Z)({defaultClassName:"".concat(c("pro-table"))},n))})})})};Ve.Summary=Se.Z.Summary;var Ae=Ve},86413:function(Cn,ve,e){"use strict";e.d(ve,{Q:function(){return t}});var r=e(1413),a=e(85893),F=e(68508),C=e(51812),N=e(1977),$=e(73177),t=function(T){var h=(0,N.n)((0,$.b)(),"4.24.0")>-1?{menu:T}:{overlay:(0,a.jsx)(F.Z,(0,r.Z)({},T))};return(0,C.Y)(h)}},90081:function(Cn,ve,e){"use strict";e.d(ve,{U:function(){return we}});var r=e(91),a=e(1413),F=e(97685),C=e(85893),N=e(50888),$=e(73177),t=e(46986),j=e(55241),T=e(43825),h=e(67294),Z=e(4942),pe=e(14855),L=function(M){var Ie="".concat(M.antCls,"-progress-bg");return(0,Z.Z)({},M.componentCls,{"&-multiple":{paddingBlockStart:6,paddingBlockEnd:12,paddingInline:8},"&-progress":{"&-success":(0,Z.Z)({},Ie,{backgroundColor:M.colorSuccess}),"&-error":(0,Z.Z)({},Ie,{backgroundColor:M.colorError}),"&-warning":(0,Z.Z)({},Ie,{backgroundColor:M.colorWarning})},"&-rule":{display:"flex",alignItems:"center","&-icon":{"&-default":{display:"flex",alignItems:"center",justifyContent:"center",width:"14px",height:"22px","&-circle":{width:"6px",height:"6px",backgroundColor:M.colorTextSecondary,borderRadius:"4px"}},"&-loading":{color:M.colorPrimary},"&-error":{color:M.colorError},"&-success":{color:M.colorSuccess}},"&-text":{color:M.colorText}}})};function ye(ee){return(0,pe.Xj)("InlineErrorFormItem",function(M){var Ie=(0,a.Z)((0,a.Z)({},M),{},{componentCls:".".concat(ee)});return[L(Ie)]})}var Ce=["label","rules","name","children","popoverProps"],je=["errorType","rules","name","popoverProps","children"],B={marginBlockStart:-5,marginBlockEnd:-5,marginInlineStart:0,marginInlineEnd:0},H=function(M){var Ie=M.inputProps,ue=M.input,Ee=M.extra,_e=M.errorList,xt=M.popoverProps,be=(0,h.useState)(!1),Nn=(0,F.Z)(be,2),ge=Nn[0],me=Nn[1],Oe=(0,h.useState)([]),dt=(0,F.Z)(Oe,2),Te=dt[0],ke=dt[1],Kt=(0,h.useContext)(t.ZP.ConfigContext),Pn=Kt.getPrefixCls,qe=Pn(),A=ye("".concat(qe,"-form-item-with-help")),pt=A.wrapSSR,Zt=A.hashId;(0,h.useEffect)(function(){Ie.validateStatus!=="validating"&&ke(Ie.errors)},[Ie.errors,Ie.validateStatus]);var Lt=(0,$.X)(Te.length<1?!1:ge,function(Wt){Wt!==ge&&me(Wt)});return(0,C.jsx)(j.Z,(0,a.Z)((0,a.Z)((0,a.Z)({trigger:(xt==null?void 0:xt.trigger)||"focus",placement:(xt==null?void 0:xt.placement)||"topRight"},Lt),{},{getPopupContainer:xt==null?void 0:xt.getPopupContainer,getTooltipContainer:xt==null?void 0:xt.getTooltipContainer,content:pt((0,C.jsxs)("div",{className:"".concat(qe,"-form-item-with-help ").concat(Zt),children:[Ie.validateStatus==="validating"?(0,C.jsx)(N.Z,{}):null,_e]}))},xt),{},{children:(0,C.jsxs)("div",{children:[ue,Ee]})}),"popover")},Se=function(M){var Ie=M.label,ue=M.rules,Ee=M.name,_e=M.children,xt=M.popoverProps,be=(0,r.Z)(M,Ce);return(0,C.jsx)(T.Z.Item,(0,a.Z)((0,a.Z)({preserve:!1,name:Ee,rules:ue,hasFeedback:!0,_internalItemRender:{mark:"pro_table_render",render:function(ge,me){return(0,C.jsx)(H,(0,a.Z)({inputProps:ge,popoverProps:xt},me))}}},be),{},{style:(0,a.Z)((0,a.Z)({},B),be==null?void 0:be.style),children:_e}))},we=function(M){var Ie=M.errorType,ue=M.rules,Ee=M.name,_e=M.popoverProps,xt=M.children,be=(0,r.Z)(M,je);return Ee&&(ue==null?void 0:ue.length)&&Ie==="popover"?(0,C.jsx)(Se,(0,a.Z)((0,a.Z)({name:Ee,rules:ue,popoverProps:_e},be),{},{children:xt})):(0,C.jsx)(T.Z.Item,(0,a.Z)((0,a.Z)({rules:ue},be),{},{style:(0,a.Z)((0,a.Z)({},B),be.style),name:Ee,children:xt}))}},77398:function(Cn,ve,e){"use strict";e.d(ve,{X:function(){return N}});var r=e(85893),a=e(17057),F=function(t){var j;return!!(!(t==null||(j=t.valueType)===null||j===void 0)&&j.toString().startsWith("date")||(t==null?void 0:t.valueType)==="select"||(t==null?void 0:t.valueEnum))},C=function(t){var j;return((j=t.ellipsis)===null||j===void 0?void 0:j.showTitle)===!1?!1:t.ellipsis},N=function(t,j,T){if(j.copyable||j.ellipsis){var h=j.copyable&&T?{text:T,tooltips:["",""]}:void 0,Z=F(j),pe=C(j)&&T?{tooltip:(j==null?void 0:j.tooltip)!==!1&&Z?(0,r.jsx)("div",{className:"pro-table-tooltip-text",children:t}):T}:!1;return(0,r.jsx)(a.Z.Text,{style:{width:"100%",margin:0,padding:0},title:"",copyable:h,ellipsis:pe,children:t})}return t}},2026:function(Cn,ve,e){"use strict";e.d(ve,{w:function(){return a}});var r=e(22270),a=function(C,N,$){return N===void 0?C:(0,r.h)(C,N,$)}},28700:function(Cn,ve,e){"use strict";e.d(ve,{v:function(){return r}});var r=function(F){if(F&&F!==!0)return F}},86671:function(Cn,ve,e){"use strict";e.d(ve,{CB:function(){return qe},aX:function(){return Pn},cx:function(){return dt},sN:function(){return Oe}});var r=e(74902),a=e(74165),F=e(84506),C=e(15861),N=e(97685),$=e(4942),t=e(91),j=e(1413),T=e(71002),h=e(85893),Z=e(50888),pe=e(73964),L=e(2453),ye=e(43825),Ce=e(86738),je=e(84164),B=e(60869),H=e(94787),Se=e(20059),we=e(45520),ee=e(67294),M=e(48171),Ie=e(10178),ue=e(41036),Ee=e(27068),_e=e(26369),xt=e(92210),be=["map_row_parentKey"],Nn=["map_row_parentKey","map_row_key"],ge=["map_row_key"],me=function(pt){return(L.ZP.warn||L.ZP.warning)(pt)},Oe=function(pt){return Array.isArray(pt)?pt.join(","):pt};function dt(A,pt){var Zt,Lt=A.getRowKey,Wt=A.row,$t=A.data,Qt=A.childrenColumnName,mn=(Zt=Oe(A.key))===null||Zt===void 0?void 0:Zt.toString(),le=new Map;function We(Rt,zt,wt){Rt.forEach(function(Xe,ft){var gt=(wt||0)*10+ft,_t=Lt(Xe,gt).toString();Xe&&(0,T.Z)(Xe)==="object"&&Qt in Xe&&We(Xe[Qt]||[],_t,gt);var Be=(0,j.Z)((0,j.Z)({},Xe),{},{map_row_key:_t,children:void 0,map_row_parentKey:zt});delete Be.children,zt||delete Be.map_row_parentKey,le.set(_t,Be)})}pt==="top"&&le.set(mn,(0,j.Z)((0,j.Z)({},le.get(mn)),Wt)),We($t),pt==="update"&&le.set(mn,(0,j.Z)((0,j.Z)({},le.get(mn)),Wt)),pt==="delete"&&le.delete(mn);var He=function(zt){var wt=new Map,Xe=[],ft=function(){var _t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!1;zt.forEach(function(Be){if(Be.map_row_parentKey&&!Be.map_row_key){var ce=Be.map_row_parentKey,Ge=(0,t.Z)(Be,be);if(wt.has(ce)||wt.set(ce,[]),_t){var Ye;(Ye=wt.get(ce))===null||Ye===void 0||Ye.push(Ge)}}})};return ft(pt==="top"),zt.forEach(function(gt){if(gt.map_row_parentKey&>.map_row_key){var _t,Be=gt.map_row_parentKey,ce=gt.map_row_key,Ge=(0,t.Z)(gt,Nn);wt.has(ce)&&(Ge[Qt]=wt.get(ce)),wt.has(Be)||wt.set(Be,[]),(_t=wt.get(Be))===null||_t===void 0||_t.push(Ge)}}),ft(pt==="update"),zt.forEach(function(gt){if(!gt.map_row_parentKey){var _t=gt.map_row_key,Be=(0,t.Z)(gt,ge);if(_t&&wt.has(_t)){var ce=(0,j.Z)((0,j.Z)({},Be),{},(0,$.Z)({},Qt,wt.get(_t)));Xe.push(ce);return}Xe.push(Be)}}),Xe};return He(le)}function Te(A,pt){var Zt=A.recordKey,Lt=A.onSave,Wt=A.row,$t=A.children,Qt=A.newLineConfig,mn=A.editorType,le=A.tableName,We=(0,ee.useContext)(ue.J),He=ye.Z.useFormInstance(),Rt=(0,B.Z)(!1),zt=(0,N.Z)(Rt,2),wt=zt[0],Xe=zt[1],ft=(0,M.J)((0,C.Z)((0,a.Z)().mark(function gt(){var _t,Be,ce,Ge,Ye,Ot,z,V,k;return(0,a.Z)().wrap(function(vt){for(;;)switch(vt.prev=vt.next){case 0:return vt.prev=0,Be=mn==="Map",ce=[le,Array.isArray(Zt)?Zt[0]:Zt].map(function(qt){return qt==null?void 0:qt.toString()}).flat(1).filter(Boolean),Xe(!0),vt.next=6,He.validateFields(ce,{recursive:!0});case 6:return Ge=((_t=We.getFieldFormatValue)===null||_t===void 0?void 0:_t.call(We,ce))||He.getFieldValue(ce),Array.isArray(Zt)&&Zt.length>1&&(Ye=(0,F.Z)(Zt),Ot=Ye.slice(1),z=(0,H.default)(Ge,Ot),(0,Se.Z)(Ge,Ot,z)),V=Be?(0,Se.Z)({},ce,Ge,!0):Ge,vt.next=11,Lt==null?void 0:Lt(Zt,(0,xt.T)({},Wt,V),Wt,Qt);case 11:return k=vt.sent,Xe(!1),vt.abrupt("return",k);case 16:throw vt.prev=16,vt.t0=vt.catch(0),console.log(vt.t0),Xe(!1),vt.t0;case 21:case"end":return vt.stop()}},gt,null,[[0,16]])})));return(0,ee.useImperativeHandle)(pt,function(){return{save:ft}}),(0,h.jsxs)("a",{onClick:function(){var gt=(0,C.Z)((0,a.Z)().mark(function Be(ce){return(0,a.Z)().wrap(function(Ye){for(;;)switch(Ye.prev=Ye.next){case 0:return ce.stopPropagation(),ce.preventDefault(),Ye.prev=2,Ye.next=5,ft();case 5:Ye.next=9;break;case 7:Ye.prev=7,Ye.t0=Ye.catch(2);case 9:case"end":return Ye.stop()}},Be,null,[[2,7]])}));function _t(Be){return gt.apply(this,arguments)}return _t}(),children:[wt?(0,h.jsx)(Z.Z,{style:{marginInlineEnd:8}}):null,$t||"\u4FDD\u5B58"]},"save")}var ke=function(pt){var Zt=pt.recordKey,Lt=pt.onDelete,Wt=pt.row,$t=pt.children,Qt=pt.deletePopconfirmMessage,mn=(0,B.Z)(function(){return!1}),le=(0,N.Z)(mn,2),We=le[0],He=le[1],Rt=(0,M.J)((0,C.Z)((0,a.Z)().mark(function zt(){var wt;return(0,a.Z)().wrap(function(ft){for(;;)switch(ft.prev=ft.next){case 0:return ft.prev=0,He(!0),ft.next=4,Lt==null?void 0:Lt(Zt,Wt);case 4:return wt=ft.sent,He(!1),ft.abrupt("return",wt);case 9:return ft.prev=9,ft.t0=ft.catch(0),console.log(ft.t0),He(!1),ft.abrupt("return",null);case 14:case"end":return ft.stop()}},zt,null,[[0,9]])})));return $t!==!1?(0,h.jsx)(Ce.Z,{title:Qt,onConfirm:function(){return Rt()},children:(0,h.jsxs)("a",{children:[We?(0,h.jsx)(Z.Z,{style:{marginInlineEnd:8}}):null,$t||"\u5220\u9664"]})},"delete"):null},Kt=function(pt){var Zt=pt.recordKey,Lt=pt.tableName,Wt=pt.newLineConfig,$t=pt.editorType,Qt=pt.onCancel,mn=pt.cancelEditable,le=pt.row,We=pt.cancelText,He=(0,ee.useContext)(ue.J),Rt=ye.Z.useFormInstance();return(0,h.jsx)("a",{onClick:function(){var zt=(0,C.Z)((0,a.Z)().mark(function Xe(ft){var gt,_t,Be,ce,Ge,Ye;return(0,a.Z)().wrap(function(z){for(;;)switch(z.prev=z.next){case 0:return ft.stopPropagation(),ft.preventDefault(),_t=$t==="Map",Be=[Lt,Zt].flat(1).filter(Boolean),ce=((gt=He.getFieldFormatValue)===null||gt===void 0?void 0:gt.call(He,Be))||Rt.getFieldValue(Be),Ge=_t?(0,Se.Z)({},Be,ce):ce,z.next=8,Qt==null?void 0:Qt(Zt,Ge,le,Wt);case 8:return Ye=z.sent,mn(Zt),Rt.setFieldsValue((0,$.Z)({},Zt,_t?(0,H.default)(le,Be):le)),z.abrupt("return",Ye);case 12:case"end":return z.stop()}},Xe)}));function wt(Xe){return zt.apply(this,arguments)}return wt}(),children:We||"\u53D6\u6D88"},"cancel")};function Pn(A,pt){var Zt=pt.recordKey,Lt=pt.newLineConfig,Wt=pt.saveText,$t=pt.deleteText,Qt=(0,ee.forwardRef)(Te),mn=(0,ee.createRef)();return{save:(0,h.jsx)(Qt,(0,j.Z)((0,j.Z)({},pt),{},{row:A,ref:mn,children:Wt}),"save"+Zt),saveRef:mn,delete:(Lt==null?void 0:Lt.options.recordKey)!==Zt?(0,h.jsx)(ke,(0,j.Z)((0,j.Z)({},pt),{},{row:A,children:$t}),"delete"+Zt):void 0,cancel:(0,h.jsx)(Kt,(0,j.Z)((0,j.Z)({},pt),{},{row:A}),"cancel"+Zt)}}function qe(A){var pt=(0,ee.useState)(void 0),Zt=(0,N.Z)(pt,2),Lt=Zt[0],Wt=Zt[1],$t=(0,ee.useRef)(new Map),Qt=(0,ee.useRef)(void 0);(0,Ee.Au)(function(){var $e=new Map,Re=function De(Je,mt){Je==null||Je.forEach(function(Ut,Vt){var Ze,xe=mt==null?Vt.toString():mt+"_"+Vt.toString();$e.set(xe,Oe(A.getRowKey(Ut,-1))),$e.set((Ze=Oe(A.getRowKey(Ut,-1)))===null||Ze===void 0?void 0:Ze.toString(),xe),A.childrenColumnName&&Ut[A.childrenColumnName]&&De(Ut[A.childrenColumnName],xe)})};Re(A.dataSource),$t.current=$e},[A.dataSource]),Qt.current=Lt;var mn=A.type||"single",le=(0,je.Z)(A.dataSource,"children",A.getRowKey),We=(0,N.Z)(le,1),He=We[0],Rt=(0,B.Z)([],{value:A.editableKeys,onChange:A.onChange?function($e){var Re;A==null||(Re=A.onChange)===null||Re===void 0||Re.call(A,$e.filter(function(De){return De!==void 0}),$e.map(function(De){return He(De)}).filter(function(De){return De!==void 0}))}:void 0}),zt=(0,N.Z)(Rt,2),wt=zt[0],Xe=zt[1],ft=(0,ee.useMemo)(function(){var $e=mn==="single"?wt==null?void 0:wt.slice(0,1):wt;return new Set($e)},[(wt||[]).join(","),mn]),gt=(0,_e.D)(wt),_t=(0,M.J)(function($e){var Re,De,Je,mt,Ut=(Re=A.getRowKey($e,$e.index))===null||Re===void 0||(De=Re.toString)===null||De===void 0?void 0:De.call(Re),Vt=(Je=A.getRowKey($e,-1))===null||Je===void 0||(mt=Je.toString)===null||mt===void 0?void 0:mt.call(Je),Ze=wt.map(function(oe){return oe==null?void 0:oe.toString()}),xe=(gt==null?void 0:gt.map(function(oe){return oe==null?void 0:oe.toString()}))||[],he=A.tableName&&!!(xe!=null&&xe.includes(Vt))||!!(xe!=null&&xe.includes(Ut));return{recordKey:Vt,isEditable:A.tableName&&(Ze==null?void 0:Ze.includes(Vt))||(Ze==null?void 0:Ze.includes(Ut)),preIsEditable:he}}),Be=(0,M.J)(function($e){return ft.size>0&&mn==="single"&&A.onlyOneLineEditorAlertMessage!==!1?(me(A.onlyOneLineEditorAlertMessage||"\u53EA\u80FD\u540C\u65F6\u7F16\u8F91\u4E00\u884C"),!1):(ft.add($e),Xe(Array.from(ft)),!0)}),ce=(0,M.J)(function(){var $e=(0,C.Z)((0,a.Z)().mark(function Re(De,Je){var mt,Ut;return(0,a.Z)().wrap(function(Ze){for(;;)switch(Ze.prev=Ze.next){case 0:if(mt=Oe(De).toString(),Ut=$t.current.get(mt),!(!ft.has(mt)&&Ut&&(Je!=null?Je:!0)&&A.tableName)){Ze.next=5;break}return ce(Ut,!1),Ze.abrupt("return");case 5:return Lt&&Lt.options.recordKey===De&&Wt(void 0),ft.delete(mt),ft.delete(Oe(De)),Xe(Array.from(ft)),Ze.abrupt("return",!0);case 10:case"end":return Ze.stop()}},Re)}));return function(Re,De){return $e.apply(this,arguments)}}()),Ge=(0,Ie.D)((0,C.Z)((0,a.Z)().mark(function $e(){var Re,De,Je,mt,Ut=arguments;return(0,a.Z)().wrap(function(Ze){for(;;)switch(Ze.prev=Ze.next){case 0:for(De=Ut.length,Je=new Array(De),mt=0;mt0&&mn==="single"&&A.onlyOneLineEditorAlertMessage!==!1)return me(A.onlyOneLineEditorAlertMessage||"\u53EA\u80FD\u540C\u65F6\u7F16\u8F91\u4E00\u884C"),!1;var De=A.getRowKey($e,-1);if(!De)throw(0,we.noteOnce)(!!De,`\u8BF7\u8BBE\u7F6E recordCreatorProps.record \u5E76\u8FD4\u56DE\u4E00\u4E2A\u552F\u4E00\u7684key + https://procomponents.ant.design/components/editable-table#editable-%E6%96%B0%E5%BB%BA%E8%A1%8C`),new Error("\u8BF7\u8BBE\u7F6E recordCreatorProps.record \u5E76\u8FD4\u56DE\u4E00\u4E2A\u552F\u4E00\u7684key");if(ft.add(De),Xe(Array.from(ft)),(Re==null?void 0:Re.newRecordType)==="dataSource"||A.tableName){var Je,mt={data:A.dataSource,getRowKey:A.getRowKey,row:(0,j.Z)((0,j.Z)({},$e),{},{map_row_parentKey:Re!=null&&Re.parentKey?(Je=Oe(Re==null?void 0:Re.parentKey))===null||Je===void 0?void 0:Je.toString():void 0}),key:De,childrenColumnName:A.childrenColumnName||"children"};A.setDataSource(dt(mt,(Re==null?void 0:Re.position)==="top"?"top":"update"))}else Wt({defaultValue:$e,options:(0,j.Z)((0,j.Z)({},Re),{},{recordKey:De})});return!0}),k=(0,pe.YB)(),Ke=(A==null?void 0:A.saveText)||k.getMessage("editableTable.action.save","\u4FDD\u5B58"),vt=(A==null?void 0:A.deleteText)||k.getMessage("editableTable.action.delete","\u5220\u9664"),qt=(A==null?void 0:A.cancelText)||k.getMessage("editableTable.action.cancel","\u53D6\u6D88"),Sn=(0,M.J)(function(){var $e=(0,C.Z)((0,a.Z)().mark(function Re(De,Je,mt,Ut){var Vt,Ze,xe,he,oe,Dt,Kn;return(0,a.Z)().wrap(function(d){for(;;)switch(d.prev=d.next){case 0:return he=Ut||Qt.current||{},oe=he.options,d.next=3,A==null||(Vt=A.onSave)===null||Vt===void 0?void 0:Vt.call(A,De,Je,mt,Ut);case 3:if(Dt=d.sent,ce(De),!(!(oe!=null&&oe.parentKey)&&(oe==null?void 0:oe.recordKey)===De)){d.next=8;break}return(oe==null?void 0:oe.position)==="top"?A.setDataSource([Je].concat((0,r.Z)(A.dataSource))):A.setDataSource([].concat((0,r.Z)(A.dataSource),[Je])),d.abrupt("return",Dt);case 8:return Kn={data:A.dataSource,getRowKey:A.getRowKey,row:oe?(0,j.Z)((0,j.Z)({},Je),{},{map_row_parentKey:(Ze=Oe((xe=oe==null?void 0:oe.parentKey)!==null&&xe!==void 0?xe:""))===null||Ze===void 0?void 0:Ze.toString()}):Je,key:De,childrenColumnName:A.childrenColumnName||"children"},A.setDataSource(dt(Kn,(oe==null?void 0:oe.position)==="top"?"top":"update")),d.abrupt("return",Dt);case 11:case"end":return d.stop()}},Re)}));return function(Re,De,Je,mt){return $e.apply(this,arguments)}}()),bn=(0,M.J)(function(){var $e=(0,C.Z)((0,a.Z)().mark(function Re(De,Je){var mt,Ut,Vt;return(0,a.Z)().wrap(function(xe){for(;;)switch(xe.prev=xe.next){case 0:return Ut={data:A.dataSource,getRowKey:A.getRowKey,row:Je,key:De,childrenColumnName:A.childrenColumnName||"children"},xe.next=3,A==null||(mt=A.onDelete)===null||mt===void 0?void 0:mt.call(A,De,Je);case 3:return Vt=xe.sent,xe.next=6,ce(De);case 6:return A.setDataSource(dt(Ut,"delete")),xe.abrupt("return",Vt);case 8:case"end":return xe.stop()}},Re)}));return function(Re,De){return $e.apply(this,arguments)}}()),Un=(0,M.J)(function(){var $e=(0,C.Z)((0,a.Z)().mark(function Re(De,Je,mt,Ut){var Vt,Ze;return(0,a.Z)().wrap(function(he){for(;;)switch(he.prev=he.next){case 0:return he.next=2,A==null||(Vt=A.onCancel)===null||Vt===void 0?void 0:Vt.call(A,De,Je,mt,Ut);case 2:return Ze=he.sent,he.abrupt("return",Ze);case 4:case"end":return he.stop()}},Re)}));return function(Re,De,Je,mt){return $e.apply(this,arguments)}}()),Dn=function(Re){var De=A.getRowKey(Re,Re.index),Je={saveText:Ke,cancelText:qt,deleteText:vt,addEditRecord:V,recordKey:De,cancelEditable:ce,index:Re.index,tableName:A.tableName,newLineConfig:Lt,onCancel:Un,onDelete:bn,onSave:Sn,editableKeys:wt,setEditableRowKeys:Xe,deletePopconfirmMessage:A.deletePopconfirmMessage||"".concat(k.getMessage("deleteThisLine","\u5220\u9664\u6B64\u884C"),"?")},mt=Pn(Re,Je);return A.tableName?Ot.current.set($t.current.get(Oe(De))||Oe(De),mt.saveRef):Ot.current.set(Oe(De),mt.saveRef),A.actionRender?A.actionRender(Re,Je,{save:mt.save,delete:mt.delete,cancel:mt.cancel}):[mt.save,mt.delete,mt.cancel]};return{editableKeys:wt,setEditableRowKeys:Xe,isEditable:_t,actionRender:Dn,startEditable:Be,cancelEditable:ce,addEditRecord:V,saveEditable:z,newLineRecord:Lt,preEditableKeys:gt,onValuesChange:Ye}}},86738:function(Cn,ve,e){"use strict";e.d(ve,{Z:function(){return ge}});var r=e(87462),a=e(97685),F=e(21640),C=e(94184),N=e.n(C),$=e(21770),t=e(15105),j=e(67294),T=e(98423),h=e(53124),Z=e(55241),pe=e(96159),L=e(71577),ye=e(5214),Ce=e(86743),je=e(77667),B=e(24457),H=e(81643),Se=e(60590),we=e(4942),ee=e(67968),M=function(Oe){var dt,Te,ke=Oe.componentCls,Kt=Oe.iconCls,Pn=Oe.zIndexPopup,qe=Oe.colorText,A=Oe.colorWarning,pt=Oe.marginXS,Zt=Oe.fontSize,Lt=Oe.lineHeight;return(0,we.Z)({},ke,(Te={zIndex:Pn},(0,we.Z)(Te,ke+"-inner-content",{color:qe}),(0,we.Z)(Te,ke+"-message",(dt={position:"relative",marginBottom:pt,color:qe,fontSize:Zt,display:"flex",flexWrap:"nowrap",alignItems:"start"},(0,we.Z)(dt,"> "+ke+"-message-icon "+Kt,{color:A,fontSize:Zt,flex:"none",lineHeight:1,paddingTop:(Math.round(Zt*Lt)-Zt)/2}),(0,we.Z)(dt,"&-title",{flex:"auto",marginInlineStart:pt}),dt)),(0,we.Z)(Te,ke+"-buttons",{textAlign:"end",button:{marginInlineStart:pt}}),Te))},Ie=(0,ee.Z)("Popconfirm",function(me){return M(me)},function(me){var Oe=me.zIndexPopupBase;return{zIndexPopup:Oe+60}}),ue=function(me,Oe){var dt={};for(var Te in me)Object.prototype.hasOwnProperty.call(me,Te)&&Oe.indexOf(Te)<0&&(dt[Te]=me[Te]);if(me!=null&&typeof Object.getOwnPropertySymbols=="function")for(var ke=0,Te=Object.getOwnPropertySymbols(me);ke0?Ce:h}),M=Math.ceil((Ce||h)/ee.pageSize);ee.current>M&&(ee.current=M||1);var Ie=function(_e,xt){we({current:_e!=null?_e:1,pageSize:xt||ee.pageSize})},ue=function(_e,xt){var be;Z&&((be=Z.onChange)===null||be===void 0||be.call(Z,_e,xt)),Ie(_e,xt),pe(_e,xt||(ee==null?void 0:ee.pageSize))};return Z===!1?[{},function(){}]:[(0,r.Z)((0,r.Z)({},ee),{onChange:ue}),Ie]}},93125:function(Cn,ve,e){"use strict";e.d(ve,{HK:function(){return B},TA:function(){return Se},W$:function(){return H},ZP:function(){return Ie},rM:function(){return we}});var r=e(4942),a=e(84506),F=e(87462),C=e(97685),N=e(74902),$=e(71002),t=e(80882),j=e(82327),T=e(10225),h=e(17341),Z=e(1089),pe=e(21770),L=e(67294),ye=e(32808),Ce=e(13013),je=e(78045),B={},H="SELECT_ALL",Se="SELECT_INVERT",we="SELECT_NONE",ee=[];function M(ue,Ee){var _e=[];return(ue||[]).forEach(function(xt){_e.push(xt),xt&&(0,$.Z)(xt)==="object"&&Ee in xt&&(_e=[].concat((0,N.Z)(_e),(0,N.Z)(M(xt[Ee],Ee))))}),_e}function Ie(ue,Ee){var _e=ue||{},xt=_e.preserveSelectedRowKeys,be=_e.selectedRowKeys,Nn=_e.defaultSelectedRowKeys,ge=_e.getCheckboxProps,me=_e.onChange,Oe=_e.onSelect,dt=_e.onSelectAll,Te=_e.onSelectInvert,ke=_e.onSelectNone,Kt=_e.onSelectMultiple,Pn=_e.columnWidth,qe=_e.type,A=_e.selections,pt=_e.fixed,Zt=_e.renderCell,Lt=_e.hideSelectAll,Wt=_e.checkStrictly,$t=Wt===void 0?!0:Wt,Qt=Ee.prefixCls,mn=Ee.data,le=Ee.pageData,We=Ee.getRecordByKey,He=Ee.getRowKey,Rt=Ee.expandType,zt=Ee.childrenColumnName,wt=Ee.locale,Xe=Ee.getPopupContainer,ft=(0,pe.Z)(be||Nn||ee,{value:be}),gt=(0,C.Z)(ft,2),_t=gt[0],Be=gt[1],ce=L.useRef(new Map),Ge=(0,L.useCallback)(function(Ze){if(xt){var xe=new Map;Ze.forEach(function(he){var oe=We(he);!oe&&ce.current.has(he)&&(oe=ce.current.get(he)),xe.set(he,oe)}),ce.current=xe}},[We,xt]);L.useEffect(function(){Ge(_t)},[_t]);var Ye=(0,L.useMemo)(function(){return $t?{keyEntities:null}:(0,Z.I8)(mn,{externalGetKey:He,childrenPropName:zt})},[mn,He,$t,zt]),Ot=Ye.keyEntities,z=(0,L.useMemo)(function(){return M(le,zt)},[le,zt]),V=(0,L.useMemo)(function(){var Ze=new Map;return z.forEach(function(xe,he){var oe=He(xe,he),Dt=(ge?ge(xe):null)||{};Ze.set(oe,Dt)}),Ze},[z,He,ge]),k=(0,L.useCallback)(function(Ze){var xe;return!!(!((xe=V.get(He(Ze)))===null||xe===void 0)&&xe.disabled)},[V,He]),Ke=(0,L.useMemo)(function(){if($t)return[_t||[],[]];var Ze=(0,h.S)(_t,!0,Ot,k),xe=Ze.checkedKeys,he=Ze.halfCheckedKeys;return[xe||[],he]},[_t,$t,Ot,k]),vt=(0,C.Z)(Ke,2),qt=vt[0],Sn=vt[1],bn=(0,L.useMemo)(function(){var Ze=qe==="radio"?qt.slice(0,1):qt;return new Set(Ze)},[qt,qe]),Un=(0,L.useMemo)(function(){return qe==="radio"?new Set:new Set(Sn)},[Sn,qe]),Dn=(0,L.useState)(null),$e=(0,C.Z)(Dn,2),Re=$e[0],De=$e[1];L.useEffect(function(){ue||Be(ee)},[!!ue]);var Je=(0,L.useCallback)(function(Ze,xe){var he,oe;Ge(Ze),xt?(he=Ze,oe=Ze.map(function(Dt){return ce.current.get(Dt)})):(he=[],oe=[],Ze.forEach(function(Dt){var Kn=We(Dt);Kn!==void 0&&(he.push(Dt),oe.push(Kn))})),Be(he),me==null||me(he,oe,{type:xe})},[Be,We,me,xt]),mt=(0,L.useCallback)(function(Ze,xe,he,oe){if(Oe){var Dt=he.map(function(Kn){return We(Kn)});Oe(We(Ze),xe,Dt,oe)}Je(he,"single")},[Oe,We,Je]),Ut=(0,L.useMemo)(function(){if(!A||Lt)return null;var Ze=A===!0?[H,Se,we]:A;return Ze.map(function(xe){return xe===H?{key:"all",text:wt.selectionAll,onSelect:function(){Je(mn.map(function(oe,Dt){return He(oe,Dt)}).filter(function(oe){var Dt=V.get(oe);return!(Dt!=null&&Dt.disabled)||bn.has(oe)}),"all")}}:xe===Se?{key:"invert",text:wt.selectInvert,onSelect:function(){var oe=new Set(bn);le.forEach(function(Kn,b){var d=He(Kn,b),o=V.get(d);o!=null&&o.disabled||(oe.has(d)?oe.delete(d):oe.add(d))});var Dt=Array.from(oe);Te&&Te(Dt),Je(Dt,"invert")}}:xe===we?{key:"none",text:wt.selectNone,onSelect:function(){ke==null||ke(),Je(Array.from(bn).filter(function(oe){var Dt=V.get(oe);return Dt==null?void 0:Dt.disabled}),"none")}}:xe}).map(function(xe){return(0,F.Z)((0,F.Z)({},xe),{onSelect:function(){for(var oe,Dt,Kn=arguments.length,b=new Array(Kn),d=0;d1&&arguments[1]!==void 0?arguments[1]:{},R=s.getContainer,y=R===void 0?function(){return window}:R,I=s.callback,E=s.duration,se=E===void 0?450:E,J=y(),_=M(J,!0),Ct=Date.now(),jt=function rt(){var Yt=Date.now(),sn=Yt-Ct,wn=we(sn>se?se:sn,_,m,se);ee(J)?J.scrollTo(window.pageXOffset,wn):J instanceof Document||J.constructor.name==="HTMLDocument"?J.documentElement.scrollTop=wn:J.scrollTop=wn,sn0&&arguments[0]!==void 0?arguments[0]:{confirm:!1,closeDropdown:!1},rn=Pe.confirm,jn=Pe.closeDropdown;rn&&Ft([]),jn&&Wn(!1),de(""),p(Jn?(tr||[]).map(function(sr){return String(sr)}):[])},ln=function(){var Pe=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{closeDropdown:!0},rn=Pe.closeDropdown;rn&&Wn(!1),Ft(n())},Gt=function(Pe){Pe&&Ve!==void 0&&p(Ve||[]),Wn(Pe),!Pe&&!E.filterDropdown&&re()},At=$()((0,a.Z)({},se+"-menu-without-submenu",!Ot(E.filters||[]))),Bn=function(Pe){if(Pe.target.checked){var rn=Sn(E==null?void 0:E.filters).map(function(jn){return String(jn)});p(rn)}else p([])},It=function Ht(Pe){var rn=Pe.filters;return(rn||[]).map(function(jn,sr){var ur=String(jn.value),_n={title:jn.text,key:jn.value!==void 0?ur:sr};return jn.children&&(_n.children=Ht({filters:jn.children})),_n})},xn=function Ht(Pe){var rn;return(0,C.Z)((0,C.Z)({},Pe),{text:Pe.title,value:Pe.key,children:((rn=Pe.children)===null||rn===void 0?void 0:rn.map(function(jn){return Ht(jn)}))||[]})},nn;if(typeof E.filterDropdown=="function")nn=E.filterDropdown({prefixCls:se+"-custom",setSelectedKeys:function(Pe){return c({selectedKeys:Pe})},selectedKeys:n(),confirm:ln,clearFilters:Tt,filters:E.filters,visible:hn,close:function(){Wn(!1)}});else if(E.filterDropdown)nn=E.filterDropdown;else{var te=n()||[],Et=function(){return(E.filters||[]).length===0?Z.createElement(mn.Z,{image:mn.Z.PRESENTED_IMAGE_SIMPLE,description:En.filterEmptyText,imageStyle:{height:24},style:{margin:0,padding:"16px 0"}}):jt==="tree"?Z.createElement(Z.Fragment,null,Z.createElement(_t,{filterSearch:Yt,value:ie,onChange:Fe,tablePrefixCls:y,locale:En}),Z.createElement("div",{className:y+"-filter-dropdown-tree"},_?Z.createElement($t.Z,{checked:te.length===Sn(E.filters).length,indeterminate:te.length>0&&te.length "+J+"-container",(0,a.Z)({},"> "+J+"-content, > "+J+"-body",{"> table > tbody > tr > td":(0,a.Z)({},"> "+J+"-expanded-row-fixed",{margin:"-"+Yt+"px -"+(sn+s.lineWidth)+"px"})})))};return(0,a.Z)({},J+"-wrapper",(se={},(0,a.Z)(se,""+J+J+"-bordered",(0,C.Z)((0,C.Z)((0,C.Z)((I={},(0,a.Z)(I,"> "+J+"-title",{border:_,borderBottom:0}),(0,a.Z)(I,"> "+J+"-container",(y={borderInlineStart:_},(0,a.Z)(y,` + > `+J+`-content, + > `+J+`-header, + > `+J+`-body, + > `+J+`-summary + `,{"> table":(R={},(0,a.Z)(R,` + > thead > tr > th, + > tbody > tr > td, + > tfoot > tr > th, + > tfoot > tr > td + `,{borderInlineEnd:_}),(0,a.Z)(R,"> thead",{"> tr:not(:last-child) > th":{borderBottom:_},"> tr > th::before":{backgroundColor:"transparent !important"}}),(0,a.Z)(R,` + > thead > tr, + > tbody > tr, + > tfoot > tr + `,(0,a.Z)({},"> "+J+"-cell-fix-right-first::after",{borderInlineEnd:_})),(0,a.Z)(R,"> table > tbody > tr > td",(0,a.Z)({},"> "+J+"-expanded-row-fixed",{margin:"-"+s.tablePaddingVertical+"px -"+(s.tablePaddingHorizontal+s.lineWidth)+"px","&::after":{position:"absolute",top:0,insetInlineEnd:s.lineWidth,bottom:0,borderInlineEnd:_,content:'""'}})),R)}),(0,a.Z)(y,` + > `+J+`-content, + > `+J+`-header + `,{"> table":{borderTop:_}}),y)),(0,a.Z)(I,"&"+J+"-scroll-horizontal",(0,a.Z)({},"> "+J+"-container > "+J+"-body",{"> table > tbody":(0,a.Z)({},` + > tr`+J+`-expanded-row, + > tr`+J+`-placeholder + `,{"> td":{borderInlineEnd:0}})})),I),Ct("middle",s.tablePaddingVerticalMiddle,s.tablePaddingHorizontalMiddle)),Ct("small",s.tablePaddingVerticalSmall,s.tablePaddingHorizontalSmall)),(0,a.Z)({},"> "+J+"-footer",{border:_,borderTop:0}))),(0,a.Z)(se,J+"-cell",(E={},(0,a.Z)(E,J+"-container:first-child",{borderTop:0}),(0,a.Z)(E,"&-scrollbar:not([rowspan])",{boxShadow:"0 "+s.lineWidth+"px 0 "+s.lineWidth+"px "+s.tableHeaderBg}),E)),se))},ne=G,q=e(14747),Y=function(s){var R,y=s.componentCls;return(0,a.Z)({},y+"-wrapper",(0,a.Z)({},y+"-cell-ellipsis",(0,C.Z)((0,C.Z)({},q.vS),(R={wordBreak:"keep-all"},(0,a.Z)(R,` + &`+y+`-cell-fix-left-last, + &`+y+`-cell-fix-right-first + `,(0,a.Z)({overflow:"visible"},y+"-cell-content",{display:"block",overflow:"hidden",textOverflow:"ellipsis"})),(0,a.Z)(R,y+"-column-title",{overflow:"hidden",textOverflow:"ellipsis",wordBreak:"keep-all"}),R))))},ot=Y,at=function(s){var R=s.componentCls;return(0,a.Z)({},R+"-wrapper",(0,a.Z)({},R+"-tbody > tr"+R+"-placeholder",{textAlign:"center",color:s.colorTextDisabled,"&:hover > td":{background:s.colorBgContainer}}))},ct=at,Qe=e(49867),Le=function(s){var R,y,I=s.componentCls,E=s.antCls,se=s.controlInteractiveSize,J=s.motionDurationSlow,_=s.lineWidth,Ct=s.paddingXS,jt=s.lineType,rt=s.tableBorderColor,Yt=s.tableExpandIconBg,sn=s.tableExpandColumnWidth,wn=s.borderRadius,En=s.fontSize,dn=s.fontSizeSM,On=s.lineHeight,Fn=s.tablePaddingVertical,Ln=s.tablePaddingHorizontal,Jn=s.tableExpandedRowBg,tr=s.paddingXXS,dr=se/2-_,i=dr*2+_*3,x=_+"px "+jt+" "+rt,O=tr-_;return(0,a.Z)({},I+"-wrapper",(y={},(0,a.Z)(y,I+"-expand-icon-col",{width:sn}),(0,a.Z)(y,I+"-row-expand-icon-cell",(0,a.Z)({textAlign:"center"},I+"-row-expand-icon",{display:"inline-flex",float:"none",verticalAlign:"sub"})),(0,a.Z)(y,I+"-row-indent",{height:1,float:"left"}),(0,a.Z)(y,I+"-row-expand-icon",(0,C.Z)((0,C.Z)({},(0,Qe.N)(s)),(R={position:"relative",float:"left",boxSizing:"border-box",width:i,height:i,padding:0,color:"inherit",lineHeight:i+"px",background:Yt,border:x,borderRadius:wn,transform:"scale("+se/i+")",transition:"all "+J,userSelect:"none"},(0,a.Z)(R,"&:focus, &:hover, &:active",{borderColor:"currentcolor"}),(0,a.Z)(R,"&::before, &::after",{position:"absolute",background:"currentcolor",transition:"transform "+J+" ease-out",content:'""'}),(0,a.Z)(R,"&::before",{top:dr,insetInlineEnd:O,insetInlineStart:O,height:_}),(0,a.Z)(R,"&::after",{top:O,bottom:O,insetInlineStart:dr,width:_,transform:"rotate(90deg)"}),(0,a.Z)(R,"&-collapsed::before",{transform:"rotate(-180deg)"}),(0,a.Z)(R,"&-collapsed::after",{transform:"rotate(0deg)"}),(0,a.Z)(R,"&-spaced",{"&::before, &::after":{display:"none",content:"none"},background:"transparent",border:0,visibility:"hidden"}),R))),(0,a.Z)(y,I+"-row-indent + "+I+"-row-expand-icon",{marginTop:(En*On-_*3)/2-Math.ceil((dn*1.4-_*3)/2),marginInlineEnd:Ct}),(0,a.Z)(y,"tr"+I+"-expanded-row",(0,a.Z)({"&, &:hover":{"> td":{background:Jn}}},E+"-descriptions-view",{display:"flex",table:{flex:"auto",width:"auto"}})),(0,a.Z)(y,I+"-expanded-row-fixed",{position:"relative",margin:"-"+Fn+"px -"+Ln+"px",padding:Fn+"px "+Ln+"px"}),y))},Ue=Le,ut=function(s){var R,y,I,E,se=s.componentCls,J=s.antCls,_=s.iconCls,Ct=s.tableFilterDropdownWidth,jt=s.tableFilterDropdownSearchWidth,rt=s.paddingXXS,Yt=s.paddingXS,sn=s.colorText,wn=s.lineWidth,En=s.lineType,dn=s.tableBorderColor,On=s.tableHeaderIconColor,Fn=s.fontSizeSM,Ln=s.tablePaddingHorizontal,Jn=s.borderRadius,tr=s.motionDurationSlow,dr=s.colorTextDescription,i=s.colorPrimary,x=s.colorPrimaryActive,O=s.tableHeaderFilterActiveBg,tt=s.colorTextDisabled,Rn=s.tableFilterDropdownBg,pn=s.tableFilterDropdownHeight,Wn=s.controlItemBgHover,hn=s.boxShadow,Ve=J+"-dropdown",Ae=se+"-filter-dropdown",u=J+"-tree",n=wn+"px "+En+" "+dn;return[(0,a.Z)({},se+"-wrapper",(R={},(0,a.Z)(R,se+"-filter-column",{display:"flex",justifyContent:"space-between"}),(0,a.Z)(R,se+"-filter-trigger",{position:"relative",display:"flex",alignItems:"center",marginBlock:-rt,marginInline:rt+"px "+-Ln/2+"px",padding:"0 "+rt+"px",color:On,fontSize:Fn,borderRadius:Jn,cursor:"pointer",transition:"all "+tr,"&:hover":{color:dr,background:O},"&.active":{color:i}}),R)),(0,a.Z)({},J+"-dropdown",(0,a.Z)({},Ae,(0,C.Z)((0,C.Z)({},(0,q.Wf)(s)),(I={minWidth:Ct,backgroundColor:Rn,borderRadius:Jn,boxShadow:hn},(0,a.Z)(I,Ve+"-menu",{maxHeight:pn,overflowX:"hidden",border:0,boxShadow:"none","&:empty::after":{display:"block",padding:Yt+"px 0",color:tt,fontSize:Fn,textAlign:"center",content:'"Not Found"'}}),(0,a.Z)(I,Ae+"-tree",(y={paddingBlock:Yt+"px 0",paddingInline:Yt},(0,a.Z)(y,u,{padding:0}),(0,a.Z)(y,u+"-treenode "+u+"-node-content-wrapper:hover",{backgroundColor:Wn}),(0,a.Z)(y,u+"-treenode-checkbox-checked "+u+"-node-content-wrapper",{"&, &:hover":{backgroundColor:x}}),y)),(0,a.Z)(I,Ae+"-search",{padding:Yt,borderBottom:n,"&-input":(0,a.Z)({input:{minWidth:jt}},_,{color:tt})}),(0,a.Z)(I,Ae+"-checkall",{width:"100%",marginBottom:rt,marginInlineStart:rt}),(0,a.Z)(I,Ae+"-btns",{display:"flex",justifyContent:"space-between",padding:Yt-wn+"px "+Yt+"px",overflow:"hidden",backgroundColor:"inherit",borderTop:n}),I)))),(0,a.Z)({},J+"-dropdown "+Ae+", "+Ae+"-submenu",(E={},(0,a.Z)(E,J+"-checkbox-wrapper + span",{paddingInlineStart:Yt,color:sn}),(0,a.Z)(E,"> ul",{maxHeight:"calc(100vh - 130px)",overflowX:"hidden",overflowY:"auto"}),E))]},ht=ut,Pt=function(s){var R,y,I,E=s.componentCls,se=s.lineWidth,J=s.colorSplit,_=s.motionDurationSlow,Ct=s.zIndexTableFixed,jt=s.tableBg,rt=J;return(0,a.Z)({},E+"-wrapper",(I={},(0,a.Z)(I,` + `+E+`-cell-fix-left, + `+E+`-cell-fix-right + `,{position:"sticky !important",zIndex:Ct,background:jt}),(0,a.Z)(I,` + `+E+`-cell-fix-left-first::after, + `+E+`-cell-fix-left-last::after + `,{position:"absolute",top:0,right:{_skip_check_:!0,value:0},bottom:-se,width:30,transform:"translateX(100%)",transition:"box-shadow "+_,content:'""',pointerEvents:"none"}),(0,a.Z)(I,E+"-cell-fix-left-all::after",{display:"none"}),(0,a.Z)(I,` + `+E+`-cell-fix-right-first::after, + `+E+`-cell-fix-right-last::after + `,{position:"absolute",top:0,bottom:-se,left:{_skip_check_:!0,value:0},width:30,transform:"translateX(-100%)",transition:"box-shadow "+_,content:'""',pointerEvents:"none"}),(0,a.Z)(I,E+"-container",{"&::before, &::after":{position:"absolute",top:0,bottom:0,zIndex:Ct,width:30,transition:"box-shadow "+_,content:'""',pointerEvents:"none"},"&::before":{insetInlineStart:0},"&::after":{insetInlineEnd:0}}),(0,a.Z)(I,E+"-ping-left",(R={},(0,a.Z)(R,"&:not("+E+"-has-fix-left) "+E+"-container",{position:"relative","&::before":{boxShadow:"inset 10px 0 8px -8px "+rt}}),(0,a.Z)(R,` + `+E+`-cell-fix-left-first::after, + `+E+`-cell-fix-left-last::after + `,{boxShadow:"inset 10px 0 8px -8px "+rt}),(0,a.Z)(R,E+"-cell-fix-left-last::before",{backgroundColor:"transparent !important"}),R)),(0,a.Z)(I,E+"-ping-right",(y={},(0,a.Z)(y,"&:not("+E+"-has-fix-right) "+E+"-container",{position:"relative","&::after":{boxShadow:"inset -10px 0 8px -8px "+rt}}),(0,a.Z)(y,` + `+E+`-cell-fix-right-first::after, + `+E+`-cell-fix-right-last::after + `,{boxShadow:"inset -10px 0 8px -8px "+rt}),y)),I))},yt=Pt,en=function(s){var R,y=s.componentCls,I=s.antCls;return(0,a.Z)({},y+"-wrapper",(R={},(0,a.Z)(R,y+"-pagination"+I+"-pagination",{margin:s.margin+"px 0"}),(0,a.Z)(R,y+"-pagination",{display:"flex",flexWrap:"wrap",rowGap:s.paddingXS,"> *":{flex:"none"},"&-left":{justifyContent:"flex-start"},"&-center":{justifyContent:"center"},"&-right":{justifyContent:"flex-end"}}),R))},Bt=en,an=function(s){var R,y=s.componentCls,I=s.tableRadius;return(0,a.Z)({},y+"-wrapper",(0,a.Z)({},y,(R={},(0,a.Z)(R,y+"-title",{borderRadius:I+"px "+I+"px 0 0"}),(0,a.Z)(R,y+"-title + "+y+"-container",{borderStartStartRadius:0,borderStartEndRadius:0,table:{borderRadius:0,"> thead > tr:first-child":{"th:first-child":{borderRadius:0},"th:last-child":{borderRadius:0}}}}),(0,a.Z)(R,"&-container",{borderStartStartRadius:I,borderStartEndRadius:I,"table > thead > tr:first-child":{"th:first-child":{borderStartStartRadius:I},"th:last-child":{borderStartEndRadius:I}}}),(0,a.Z)(R,"&-footer",{borderRadius:"0 0 "+I+"px "+I+"px"}),R)))},Nt=an,Xt=function(s){var R,y=s.componentCls;return(0,a.Z)({},y+"-wrapper-rtl",(R={direction:"rtl",table:{direction:"rtl"}},(0,a.Z)(R,y+"-pagination-left",{justifyContent:"flex-end"}),(0,a.Z)(R,y+"-pagination-right",{justifyContent:"flex-start"}),(0,a.Z)(R,y+"-row-expand-icon",{"&::after":{transform:"rotate(-90deg)"},"&-collapsed::before":{transform:"rotate(180deg)"},"&-collapsed::after":{transform:"rotate(0deg)"}}),R))},on=Xt,it=function(s){var R,y=s.componentCls,I=s.antCls,E=s.iconCls,se=s.fontSizeIcon,J=s.paddingXS,_=s.tableHeaderIconColor,Ct=s.tableHeaderIconColorHover;return(0,a.Z)({},y+"-wrapper",(R={},(0,a.Z)(R,y+"-selection-col",{width:s.tableSelectionColumnWidth}),(0,a.Z)(R,y+"-bordered "+y+"-selection-col",{width:s.tableSelectionColumnWidth+J*2}),(0,a.Z)(R,` + table tr th`+y+`-selection-column, + table tr td`+y+`-selection-column + `,(0,a.Z)({paddingInlineEnd:s.paddingXS,paddingInlineStart:s.paddingXS,textAlign:"center"},I+"-radio-wrapper",{marginInlineEnd:0})),(0,a.Z)(R,"table tr th"+y+"-selection-column"+y+"-cell-fix-left",{zIndex:s.zIndexTableFixed}),(0,a.Z)(R,"table tr th"+y+"-selection-column::after",{backgroundColor:"transparent !important"}),(0,a.Z)(R,y+"-selection",{position:"relative",display:"inline-flex",flexDirection:"column"}),(0,a.Z)(R,y+"-selection-extra",(0,a.Z)({position:"absolute",top:0,zIndex:1,cursor:"pointer",transition:"all "+s.motionDurationSlow,marginInlineStart:"100%",paddingInlineStart:s.tablePaddingHorizontal/4+"px"},E,{color:_,fontSize:se,verticalAlign:"baseline","&:hover":{color:Ct}})),R))},Ne=it,et=function(s){var R=s.componentCls,y=function(E,se,J,_){var Ct;return(0,a.Z)({},""+R+R+"-"+E,(Ct={fontSize:_},(0,a.Z)(Ct,` + `+R+`-title, + `+R+`-footer, + `+R+`-thead > tr > th, + `+R+`-tbody > tr > td, + tfoot > tr > th, + tfoot > tr > td + `,{padding:se+"px "+J+"px"}),(0,a.Z)(Ct,R+"-filter-trigger",{marginInlineEnd:"-"+J/2+"px"}),(0,a.Z)(Ct,R+"-expanded-row-fixed",{margin:"-"+se+"px -"+J+"px"}),(0,a.Z)(Ct,R+"-tbody",(0,a.Z)({},R+"-wrapper:only-child "+R,{marginBlock:"-"+se+"px",marginInline:s.tableExpandColumnWidth-J+"px -"+J+"px"})),(0,a.Z)(Ct,R+"-selection-column",{paddingInlineStart:J/4+"px"}),Ct))};return(0,a.Z)({},R+"-wrapper",(0,C.Z)((0,C.Z)({},y("middle",s.tablePaddingVerticalMiddle,s.tablePaddingHorizontalMiddle,s.tableFontSizeMiddle)),y("small",s.tablePaddingVerticalSmall,s.tablePaddingHorizontalSmall,s.tableFontSizeSmall)))},un=et,Tn=function(s){var R,y=s.componentCls,I=s.marginXXS,E=s.fontSizeIcon,se=s.tableHeaderIconColor,J=s.tableHeaderIconColorHover;return(0,a.Z)({},y+"-wrapper",(R={},(0,a.Z)(R,y+"-thead th"+y+"-column-has-sorters",(0,a.Z)({outline:"none",cursor:"pointer",transition:"all "+s.motionDurationSlow,"&:hover":{background:s.tableHeaderSortHoverBg,"&::before":{backgroundColor:"transparent !important"}},"&:focus-visible":{color:s.colorPrimary}},` + &`+y+`-cell-fix-left:hover, + &`+y+`-cell-fix-right:hover + `,{background:s.tableFixedHeaderSortActiveBg})),(0,a.Z)(R,y+"-thead th"+y+"-column-sort",{background:s.tableHeaderSortBg,"&::before":{backgroundColor:"transparent !important"}}),(0,a.Z)(R,"td"+y+"-column-sort",{background:s.tableBodySortBg}),(0,a.Z)(R,y+"-column-title",{position:"relative",zIndex:1,flex:1}),(0,a.Z)(R,y+"-column-sorters",{display:"flex",flex:"auto",alignItems:"center",justifyContent:"space-between","&::after":{position:"absolute",inset:0,width:"100%",height:"100%",content:'""'}}),(0,a.Z)(R,y+"-column-sorter",(0,a.Z)({marginInlineStart:I,color:se,fontSize:0,transition:"color "+s.motionDurationSlow,"&-inner":{display:"inline-flex",flexDirection:"column",alignItems:"center"},"&-up, &-down":{fontSize:E,"&.active":{color:s.colorPrimary}}},y+"-column-sorter-up + "+y+"-column-sorter-down",{marginTop:"-0.3em"})),(0,a.Z)(R,y+"-column-sorters:hover "+y+"-column-sorter",{color:J}),R))},An=Tn,ir=function(s){var R=s.componentCls,y=s.opacityLoading,I=s.tableScrollThumbBg,E=s.tableScrollThumbBgHover,se=s.tableScrollThumbSize,J=s.tableScrollBg,_=s.zIndexTableSticky,Ct=s.lineWidth+"px "+s.lineType+" "+s.tableBorderColor;return(0,a.Z)({},R+"-wrapper",(0,a.Z)({},R+"-sticky",{"&-holder":{position:"sticky",zIndex:_,background:s.colorBgContainer},"&-scroll":{position:"sticky",bottom:0,height:se+"px !important",zIndex:_,display:"flex",alignItems:"center",background:J,borderTop:Ct,opacity:y,"&:hover":{transformOrigin:"center bottom"},"&-bar":{height:se,backgroundColor:I,borderRadius:100,transition:"all "+s.motionDurationSlow+", transform none",position:"absolute",bottom:0,"&:hover, &-active":{backgroundColor:E}}}}))},Yn=ir,Mn=function(s){var R,y=s.componentCls,I=s.lineWidth,E=s.tableBorderColor,se=I+"px "+s.lineType+" "+E;return(0,a.Z)({},y+"-wrapper",(R={},(0,a.Z)(R,y+"-summary",{position:"relative",zIndex:s.zIndexTableFixed,background:s.tableBg,"> tr":{"> th, > td":{borderBottom:se}}}),(0,a.Z)(R,"div"+y+"-summary",{boxShadow:"0 -"+I+"px 0 "+E}),R))},Me=Mn,bt=function(s){var R,y,I,E=s.componentCls,se=s.fontWeightStrong,J=s.tablePaddingVertical,_=s.tablePaddingHorizontal,Ct=s.lineWidth,jt=s.lineType,rt=s.tableBorderColor,Yt=s.tableFontSize,sn=s.tableBg,wn=s.tableRadius,En=s.tableHeaderTextColor,dn=s.motionDurationSlow,On=s.tableHeaderBg,Fn=s.tableHeaderCellSplitColor,Ln=s.tableRowHoverBg,Jn=s.tableSelectedRowBg,tr=s.tableSelectedRowHoverBg,dr=s.tableFooterTextColor,i=s.tableFooterBg,x=s.paddingContentVerticalLG,O=s.wireframe,tt=Ct+"px "+jt+" "+rt;return(0,a.Z)({},E+"-wrapper",(0,C.Z)((0,C.Z)({clear:"both",maxWidth:"100%"},(0,q.dF)()),(I={},(0,a.Z)(I,E,(0,C.Z)((0,C.Z)({},(0,q.Wf)(s)),{fontSize:Yt,background:sn,borderRadius:wn})),(0,a.Z)(I,"table",{width:"100%",textAlign:"start",borderRadius:wn+"px "+wn+"px 0 0",borderCollapse:"separate",borderSpacing:0}),(0,a.Z)(I,` + `+E+`-thead > tr > th, + `+E+`-tbody > tr > td, + tfoot > tr > th, + tfoot > tr > td + `,{position:"relative",padding:x+"px "+_+"px",overflowWrap:"break-word"}),(0,a.Z)(I,E+"-title",{padding:J+"px "+_+"px"}),(0,a.Z)(I,E+"-thead",{"> tr > th":(0,a.Z)({position:"relative",color:En,fontWeight:se,textAlign:"start",background:On,borderBottom:tt,transition:"background "+dn+" ease","&[colspan]:not([colspan='1'])":{textAlign:"center"}},"&:not(:last-child):not("+E+"-selection-column):not("+E+"-row-expand-icon-cell):not([colspan])::before",{position:"absolute",top:"50%",insetInlineEnd:0,width:1,height:"1.6em",backgroundColor:Fn,transform:"translateY(-50%)",transition:"background-color "+dn,content:'""'}),"> tr:not(:last-child) > th[colspan]":{borderBottom:0}}),(0,a.Z)(I,E+"-tbody",{"> tr":(R={"> td":(0,a.Z)({borderTop:tt,transition:"background "+dn},` + > `+E+`-wrapper:only-child, + > `+E+"-expanded-row-fixed > "+E+`-wrapper:only-child + `,(0,a.Z)({},E,(0,a.Z)({marginBlock:"-"+J+"px",marginInline:s.tableExpandColumnWidth-_+"px -"+_+"px"},E+"-tbody > tr:last-child > td",{borderBottom:0,"&:first-child, &:last-child":{borderRadius:0}}))),"&:last-child > td":{borderBottom:tt}},(0,a.Z)(R,`&:first-child > td, + &`+E+"-measure-row + tr > td",{borderTop:"none"}),(0,a.Z)(R,` + &`+E+`-row:hover > td, + > td`+E+`-cell-row-hover + `,{background:Ln}),(0,a.Z)(R,"&"+E+"-row-selected",{"> td":{background:Jn},"&:hover > td":{background:tr}}),R)}),(0,a.Z)(I,E+":not("+E+"-bordered) "+E+"-tbody > tr",O?void 0:(y={},(0,a.Z)(y,"&"+E+"-row:hover, &"+E+"-row"+E+"-row-selected",(0,a.Z)({},"+ tr"+E+"-row > td",{borderTopColor:"transparent"})),(0,a.Z)(y,"&"+E+`-row:last-child:hover > td, + &`+E+"-row"+E+"-row-selected:last-child > td",{borderBottomColor:"transparent"}),(0,a.Z)(y,` + &`+E+`-row:hover > td, + > td`+E+`-cell-row-hover, + &`+E+"-row"+E+`-row-selected > td + `,{borderTopColor:"transparent","&:first-child":{borderStartStartRadius:wn,borderEndStartRadius:wn},"&:last-child":{borderStartEndRadius:wn,borderEndEndRadius:wn}}),y)),(0,a.Z)(I,E+"-footer",{padding:J+"px "+_+"px",color:dr,background:i}),I)))},Zn=(0,w.Z)("Table",function(m){var s=m.controlItemBgActive,R=m.controlItemBgActiveHover,y=m.colorTextPlaceholder,I=m.colorTextHeading,E=m.colorSplit,se=m.fontSize,J=m.padding,_=m.paddingXS,Ct=m.paddingSM,jt=m.controlHeight,rt=m.colorFillAlter,Yt=m.colorIcon,sn=m.colorIconHover,wn=m.opacityLoading,En=m.colorBgContainer,dn=m.colorFillSecondary,On=m.borderRadiusLG,Fn=m.colorFillContent,Ln=m.controlInteractiveSize,Jn=new X.C(Yt),tr=new X.C(sn),dr=s,i=2,x=new X.C(rt).onBackground(En).toHexString(),O=(0,l.TS)(m,{tableFontSize:se,tableBg:En,tableRadius:On,tablePaddingVertical:J,tablePaddingHorizontal:J,tablePaddingVerticalMiddle:Ct,tablePaddingHorizontalMiddle:_,tablePaddingVerticalSmall:_,tablePaddingHorizontalSmall:_,tableBorderColor:E,tableHeaderTextColor:I,tableHeaderBg:x,tableFooterTextColor:I,tableFooterBg:x,tableHeaderCellSplitColor:E,tableHeaderSortBg:dn,tableHeaderSortHoverBg:Fn,tableHeaderIconColor:Jn.clone().setAlpha(Jn.getAlpha()*wn).toRgbString(),tableHeaderIconColorHover:tr.clone().setAlpha(tr.getAlpha()*wn).toRgbString(),tableBodySortBg:rt,tableFixedHeaderSortActiveBg:dn,tableHeaderFilterActiveBg:Fn,tableFilterDropdownBg:En,tableRowHoverBg:x,tableSelectedRowBg:dr,tableSelectedRowHoverBg:R,zIndexTableFixed:i,zIndexTableSticky:i+1,tableFontSizeMiddle:se,tableFontSizeSmall:se,tableSelectionColumnWidth:jt,tableExpandIconBg:En,tableExpandColumnWidth:Ln+2*m.padding,tableExpandedRowBg:rt,tableFilterDropdownWidth:120,tableFilterDropdownHeight:264,tableFilterDropdownSearchWidth:140,tableScrollThumbSize:8,tableScrollThumbBg:y,tableScrollThumbBgHover:I,tableScrollBg:E});return[bt(O),Bt(O),Me(O),An(O),ht(O),ne(O),Nt(O),Ue(O),Me(O),ct(O),Ne(O),yt(O),Yn(O),ot(O),un(O),on(O)]}),tn=[];function Xn(m,s){var R,y=m.prefixCls,I=m.className,E=m.style,se=m.size,J=m.bordered,_=m.dropdownPrefixCls,Ct=m.dataSource,jt=m.pagination,rt=m.rowSelection,Yt=m.rowKey,sn=Yt===void 0?"key":Yt,wn=m.rowClassName,En=m.columns,dn=m.children,On=m.childrenColumnName,Fn=m.onChange,Ln=m.getPopupContainer,Jn=m.loading,tr=m.expandIcon,dr=m.expandable,i=m.expandedRowRender,x=m.expandIconColumnIndex,O=m.indentSize,tt=m.scroll,Rn=m.sortDirections,pn=m.locale,Wn=m.showSorterTooltip,hn=Wn===void 0?!0:Wn,Ve=Z.useMemo(function(){return En||(0,j.L)(dn)},[En,dn]),Ae=Z.useMemo(function(){return Ve.some(function(or){return or.responsive})},[Ve]),u=(0,Ce.Z)(Ae),n=Z.useMemo(function(){var or=new Set(Object.keys(u).filter(function($n){return u[$n]}));return Ve.filter(function($n){return!$n.responsive||$n.responsive.some(function(Cr){return or.has(Cr)})})},[Ve,u]),p=(0,h.Z)(m,["className","style","columns"]),c=Z.useContext(ye.Z),S=Z.useContext(pe.E_),K=S.locale,ze=K===void 0?je.Z:K,W=S.renderEmpty,Q=S.direction,ae=se||c,nt=(0,C.Z)((0,C.Z)({},ze.Table),pn),st=Ct||tn,ie=Z.useContext(pe.E_),de=ie.getPrefixCls,Fe=de("table",y),Ft=de("dropdown",_),re=(0,C.Z)({childrenColumnName:On,expandIconColumnIndex:x},dr),Tt=re.childrenColumnName,ln=Tt===void 0?"children":Tt,Gt=Z.useMemo(function(){return st.some(function(or){return or==null?void 0:or[ln]})?"nest":i||dr&&dr.expandedRowRender?"row":null},[st]),At={body:Z.useRef()},Bn=Z.useMemo(function(){return typeof sn=="function"?sn:function(or){return or==null?void 0:or[sn]}},[sn]),It=(0,Re.Z)(st,ln,Bn),xn=(0,F.Z)(It,1),nn=xn[0],te={},Et=function($n,Cr){var lr=arguments.length>2&&arguments[2]!==void 0?arguments[2]:!1,hr=(0,C.Z)((0,C.Z)({},te),$n);lr&&(te.resetPagination(),hr.pagination.current&&(hr.pagination.current=1),jt&&jt.onChange&&jt.onChange(1,hr.pagination.pageSize)),tt&&tt.scrollToFirstRowOnChange!==!1&&At.body.current&&Ie(0,{getContainer:function(){return At.body.current}}),Fn==null||Fn(hr.pagination,hr.filters,hr.sorter,{currentDataSource:Un(D(st,hr.sorterStates,ln),hr.filterStates),action:Cr})},fn=function($n,Cr){Et({sorter:$n,sorterStates:Cr},"sort",!1)},St=P({prefixCls:Fe,mergedColumns:n,onSorterChange:fn,sortDirections:Rn||["ascend","descend"],tableLocale:nt,showSorterTooltip:hn}),Mt=(0,F.Z)(St,4),lt=Mt[0],vn=Mt[1],Ht=Mt[2],Pe=Mt[3],rn=Z.useMemo(function(){return D(st,vn,ln)},[st,vn]);te.sorter=Pe(),te.sorterStates=vn;var jn=function($n,Cr){Et({filters:$n,filterStates:Cr},"filter",!0)},sr=$e({prefixCls:Fe,locale:nt,dropdownPrefixCls:Ft,mergedColumns:n,onFilterChange:jn,getPopupContainer:Ln}),ur=(0,F.Z)(sr,3),_n=ur[0],Gn=ur[1],Hn=ur[2],gn=Un(rn,Gn);te.filters=Hn,te.filterStates=Gn;var Vn=Z.useMemo(function(){var or={};return Object.keys(Hn).forEach(function($n){Hn[$n]!==null&&(or[$n]=Hn[$n])}),(0,C.Z)((0,C.Z)({},Ht),{filters:or})},[Ht,Hn]),kn=fe(Vn),ar=(0,F.Z)(kn,1),mr=ar[0],cr=function($n,Cr){Et({pagination:(0,C.Z)((0,C.Z)({},te.pagination),{current:$n,pageSize:Cr})},"paginate")},pr=(0,De.ZP)(gn.length,jt,cr),cn=(0,F.Z)(pr,2),kt=cn[0],nr=cn[1];te.pagination=jt===!1?{}:(0,De.G6)(jt,kt),te.resetPagination=nr;var fr=Z.useMemo(function(){if(jt===!1||!kt.pageSize)return gn;var or=kt.current,$n=or===void 0?1:or,Cr=kt.total,lr=kt.pageSize,hr=lr===void 0?De.L8:lr;return gn.lengthhr?gn.slice(($n-1)*hr,$n*hr):gn:gn.slice(($n-1)*hr,$n*hr)},[!!jt,gn,kt&&kt.current,kt&&kt.pageSize,kt&&kt.total]),xr=(0,Je.ZP)(rt,{prefixCls:Fe,data:gn,pageData:fr,getRowKey:Bn,getRecordByKey:nn,expandType:Gt,childrenColumnName:ln,locale:nt,getPopupContainer:Ln}),gr=(0,F.Z)(xr,2),Sr=gr[0],Zr=gr[1],yr=function($n,Cr,lr){var hr;return typeof wn=="function"?hr=$()(wn($n,Cr,lr)):hr=$()(wn),$()((0,a.Z)({},Fe+"-row-selected",Zr.has(Bn($n,Cr))),hr)};re.__PARENT_RENDER_ICON__=re.expandIcon,re.expandIcon=re.expandIcon||tr||Nn(nt),Gt==="nest"&&re.expandIconColumnIndex===void 0?re.expandIconColumnIndex=rt?1:0:re.expandIconColumnIndex>0&&rt&&(re.expandIconColumnIndex-=1),typeof re.indentSize!="number"&&(re.indentSize=typeof O=="number"?O:15);var Ir=Z.useCallback(function(or){return mr(Sr(_n(lt(or))))},[lt,_n,Sr]),rr,Rr;if(jt!==!1&&(kt==null?void 0:kt.total)){var Fr;kt.size?Fr=kt.size:Fr=ae==="small"||ae==="middle"?"small":void 0;var wr=function($n){return Z.createElement(B.Z,(0,C.Z)({},kt,{className:$()(Fe+"-pagination "+Fe+"-pagination-"+$n,kt.className),size:Fr}))},Mr=Q==="rtl"?"left":"right",Pr=kt.position;if(Pr!==null&&Array.isArray(Pr)){var Lr=Pr.find(function(or){return or.includes("top")}),Dr=Pr.find(function(or){return or.includes("bottom")}),Br=Pr.every(function(or){return""+or=="none"});!Lr&&!Dr&&!Br&&(Rr=wr(Mr)),Lr&&(rr=wr(Lr.toLowerCase().replace("top",""))),Dr&&(Rr=wr(Dr.toLowerCase().replace("bottom","")))}else Rr=wr(Mr)}var Or;typeof Jn=="boolean"?Or={spinning:Jn}:(0,r.Z)(Jn)==="object"&&(Or=(0,C.Z)({spinning:!0},Jn));var $r=Zn(Fe),Kr=(0,F.Z)($r,2),Ar=Kr[0],zr=Kr[1],Hr=$()(Fe+"-wrapper",(0,a.Z)({},Fe+"-wrapper-rtl",Q==="rtl"),I,zr);return Ar(Z.createElement("div",{ref:s,className:Hr,style:E},Z.createElement(H.Z,(0,C.Z)({spinning:!1},Or),rr,Z.createElement(t.ZP,(0,C.Z)({},p,{columns:n,direction:Q,expandable:re,prefixCls:Fe,className:$()((R={},(0,a.Z)(R,Fe+"-middle",ae==="middle"),(0,a.Z)(R,Fe+"-small",ae==="small"),(0,a.Z)(R,Fe+"-bordered",J),(0,a.Z)(R,Fe+"-empty",st.length===0),R)),data:fr,rowKey:Bn,rowClassName:yr,emptyText:pn&&pn.emptyText||(W||L.Z)("Table"),internalHooks:T.R,internalRefs:At,transformColumns:Ir})),Rr)))}var In=Z.forwardRef(Xn),zn=In;zn.SELECTION_COLUMN=Je.HK,zn.EXPAND_COLUMN=t.ZP.EXPAND_COLUMN,zn.SELECTION_ALL=Je.W$,zn.SELECTION_INVERT=Je.TA,zn.SELECTION_NONE=Je.rM,zn.Column=Ee,zn.ColumnGroup=xt,zn.Summary=t.ER;var Qn=zn,qn=Qn},31673:function(Cn,ve,e){"use strict";e.d(ve,{Z:function(){return mn}});var r=e(31343),a=e(4942),F=e(71002),C=e(97685),N=e(87462),$=e(1413),t=e(67294),j={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M300 276.5a56 56 0 1056-97 56 56 0 00-56 97zm0 284a56 56 0 1056-97 56 56 0 00-56 97zM640 228a56 56 0 10112 0 56 56 0 00-112 0zm0 284a56 56 0 10112 0 56 56 0 00-112 0zM300 844.5a56 56 0 1056-97 56 56 0 00-56 97zM640 796a56 56 0 10112 0 56 56 0 00-112 0z"}}]},name:"holder",theme:"outlined"},T=j,h=e(84089),Z=function(We,He){return t.createElement(h.Z,(0,$.Z)((0,$.Z)({},We),{},{ref:He,icon:T}))};Z.displayName="HolderOutlined";var pe=t.forwardRef(Z),L=e(94184),ye=e.n(L),Ce=e(53124),je=e(33603),B=4;function H(le){var We,He=le.dropPosition,Rt=le.dropLevelOffset,zt=le.prefixCls,wt=le.indent,Xe=le.direction,ft=Xe===void 0?"ltr":Xe,gt=ft==="ltr"?"left":"right",_t=ft==="ltr"?"right":"left",Be=(We={},(0,a.Z)(We,gt,-Rt*wt+B),(0,a.Z)(We,_t,0),We);switch(He){case-1:Be.top=-3;break;case 1:Be.bottom=-3;break;default:Be.bottom=-3,Be[gt]=wt+B;break}return t.createElement("div",{style:Be,className:zt+"-drop-indicator"})}var Se=e(23797),we=e(32157),ee=t.forwardRef(function(le,We){var He,Rt=t.useContext(Ce.E_),zt=Rt.getPrefixCls,wt=Rt.direction,Xe=Rt.virtual,ft=le.prefixCls,gt=le.className,_t=le.showIcon,Be=_t===void 0?!1:_t,ce=le.showLine,Ge=le.switcherIcon,Ye=le.blockNode,Ot=Ye===void 0?!1:Ye,z=le.children,V=le.checkable,k=V===void 0?!1:V,Ke=le.selectable,vt=Ke===void 0?!0:Ke,qt=le.draggable,Sn=le.motion,bn=zt("tree",ft),Un=zt(),Dn=Sn!=null?Sn:(0,N.Z)((0,N.Z)({},(0,je.ZP)(Un)),{motionAppear:!1}),$e=(0,N.Z)((0,N.Z)({},le),{checkable:k,selectable:vt,showIcon:Be,motion:Dn,blockNode:Ot,showLine:Boolean(ce),dropIndicatorRender:H}),Re=(0,we.ZP)(bn),De=(0,C.Z)(Re,2),Je=De[0],mt=De[1],Ut=t.useMemo(function(){if(!qt)return!1;var Vt={};switch((0,F.Z)(qt)){case"function":Vt.nodeDraggable=qt;break;case"object":Vt=(0,N.Z)({},qt);break;default:break}return Vt.icon!==!1&&(Vt.icon=Vt.icon||t.createElement(pe,null)),Vt},[qt]);return Je(t.createElement(r.Z,(0,N.Z)({itemHeight:20,ref:We,virtual:Xe},$e,{prefixCls:bn,className:ye()((He={},(0,a.Z)(He,bn+"-icon-hide",!Be),(0,a.Z)(He,bn+"-block-node",Ot),(0,a.Z)(He,bn+"-unselectable",!vt),(0,a.Z)(He,bn+"-rtl",wt==="rtl"),He),gt,mt),direction:wt,checkable:k&&t.createElement("span",{className:bn+"-checkbox-inner"}),selectable:vt,switcherIcon:function(Ze){return(0,Se.Z)(bn,Ge,ce,Ze)},draggable:Ut}),z))}),M=ee,Ie=e(74902),ue=e(26911),Ee={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M928 444H820V330.4c0-17.7-14.3-32-32-32H473L355.7 186.2a8.15 8.15 0 00-5.5-2.2H96c-17.7 0-32 14.3-32 32v592c0 17.7 14.3 32 32 32h698c13 0 24.8-7.9 29.7-20l134-332c1.5-3.8 2.3-7.9 2.3-12 0-17.7-14.3-32-32-32zM136 256h188.5l119.6 114.4H748V444H238c-13 0-24.8 7.9-29.7 20L136 643.2V256zm635.3 512H159l103.3-256h612.4L771.3 768z"}}]},name:"folder-open",theme:"outlined"},_e=Ee,xt=function(We,He){return t.createElement(h.Z,(0,$.Z)((0,$.Z)({},We),{},{ref:He,icon:_e}))};xt.displayName="FolderOpenOutlined";var be=t.forwardRef(xt),Nn={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M880 298.4H521L403.7 186.2a8.15 8.15 0 00-5.5-2.2H144c-17.7 0-32 14.3-32 32v592c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V330.4c0-17.7-14.3-32-32-32zM840 768H184V256h188.5l119.6 114.4H840V768z"}}]},name:"folder",theme:"outlined"},ge=Nn,me=function(We,He){return t.createElement(h.Z,(0,$.Z)((0,$.Z)({},We),{},{ref:He,icon:ge}))};me.displayName="FolderOutlined";var Oe=t.forwardRef(me),dt=e(10225),Te=e(1089),ke;(function(le){le[le.None=0]="None",le[le.Start=1]="Start",le[le.End=2]="End"})(ke||(ke={}));function Kt(le,We){function He(Rt){var zt=Rt.key,wt=Rt.children;We(zt,Rt)!==!1&&Kt(wt||[],We)}le.forEach(He)}function Pn(le){var We=le.treeData,He=le.expandedKeys,Rt=le.startKey,zt=le.endKey,wt=[],Xe=ke.None;if(Rt&&Rt===zt)return[Rt];if(!Rt||!zt)return[];function ft(gt){return gt===Rt||gt===zt}return Kt(We,function(gt){if(Xe===ke.End)return!1;if(ft(gt)){if(wt.push(gt),Xe===ke.None)Xe=ke.Start;else if(Xe===ke.Start)return Xe=ke.End,!1}else Xe===ke.Start&&wt.push(gt);return He.includes(gt)}),wt}function qe(le,We){var He=(0,Ie.Z)(We),Rt=[];return Kt(le,function(zt,wt){var Xe=He.indexOf(zt);return Xe!==-1&&(Rt.push(wt),He.splice(Xe,1)),!!He.length}),Rt}var A=function(le,We){var He={};for(var Rt in le)Object.prototype.hasOwnProperty.call(le,Rt)&&We.indexOf(Rt)<0&&(He[Rt]=le[Rt]);if(le!=null&&typeof Object.getOwnPropertySymbols=="function")for(var zt=0,Rt=Object.getOwnPropertySymbols(le);ztV){var Dn=V-k;return Ke.push(String(qt).slice(0,Dn)),Ke}Ke.push(qt),k=Un}return z}var mn=0,le=1,We=2,He=3,Rt=4,zt=function(V){var k=V.enabledMeasure,Ke=V.children,vt=V.text,qt=V.width,Sn=V.fontSize,bn=V.rows,Un=V.onEllipsis,Dn=H.useState([0,0,0]),$e=(0,C.Z)(Dn,2),Re=(0,C.Z)($e[0],3),De=Re[0],Je=Re[1],mt=Re[2],Ut=$e[1],Vt=H.useState(mn),Ze=(0,C.Z)(Vt,2),xe=Ze[0],he=Ze[1],oe=H.useState(0),Dt=(0,C.Z)(oe,2),Kn=Dt[0],b=Dt[1],d=H.useRef(null),o=H.useRef(null),f=H.useMemo(function(){return(0,L.Z)(vt)},[vt]),v=H.useMemo(function(){return $t(f)},[f]),g=H.useMemo(function(){return!k||xe!==He?Ke(f,!1):Ke(Qt(f,Je),Je1&&Ct,sn=function(Ae){var u;ir(!0),(u=I.onExpand)===null||u===void 0||u.call(I,Ae)},wn=H.useState(0),En=(0,C.Z)(wn,2),dn=En[0],On=En[1],Fn=H.useState(0),Ln=(0,C.Z)(Fn,2),Jn=Ln[0],tr=Ln[1],dr=function(Ae,u){var n=Ae.offsetWidth,p;On(n),tr(parseInt((p=window.getComputedStyle)===null||p===void 0?void 0:p.call(window,u).fontSize,10)||0)},i=function(Ae){var u;bt(Ae),Me!==Ae&&((u=I.onEllipsis)===null||u===void 0||u.call(I,Ae))};H.useEffect(function(){var Ve=Dt.current;if(y&&Ct&&Ve){var Ae=Yt?Ve.offsetHeight textarea, + h`+we+` + `]=t(B["fontSizeHeading"+we],B["lineHeightHeading"+we],B.colorTextHeading,B)}),Se},T=function(B){var H=B.componentCls;return{"a&, a":(0,a.Z)((0,a.Z)({},(0,$.N)(B)),(0,r.Z)({textDecoration:B.linkDecoration,"&:active, &:hover":{textDecoration:B.linkHoverDecoration}},"&[disabled], &"+H+"-disabled",{color:B.colorTextDisabled,cursor:"not-allowed","&:active, &:hover":{color:B.colorTextDisabled},"&:active":{pointerEvents:"none"}}))}},h=function(){return{code:{margin:"0 0.2em",paddingInline:"0.4em",paddingBlock:"0.2em 0.1em",fontSize:"85%",background:"rgba(150, 150, 150, 0.1)",border:"1px solid rgba(100, 100, 100, 0.2)",borderRadius:3},kbd:{margin:"0 0.2em",paddingInline:"0.4em",paddingBlock:"0.15em 0.1em",fontSize:"90%",background:"rgba(150, 150, 150, 0.06)",border:"1px solid rgba(100, 100, 100, 0.2)",borderBottomWidth:2,borderRadius:3},mark:{padding:0,backgroundColor:C.gold[2]},"u, ins":{textDecoration:"underline",textDecorationSkipInk:"auto"},"s, del":{textDecoration:"line-through"},strong:{fontWeight:600},"ul, ol":{marginInline:0,marginBlock:"0 1em",padding:0,li:{marginInline:"20px 0",marginBlock:0,paddingInline:"4px 0",paddingBlock:0}},ul:{listStyleType:"circle",ul:{listStyleType:"disc"}},ol:{listStyleType:"decimal"},"pre, blockquote":{margin:"1em 0"},pre:{padding:"0.4em 0.6em",whiteSpace:"pre-wrap",wordWrap:"break-word",background:"rgba(150, 150, 150, 0.1)",border:"1px solid rgba(100, 100, 100, 0.2)",borderRadius:3,code:{display:"inline",margin:0,padding:0,fontSize:"inherit",fontFamily:"inherit",background:"transparent",border:0}},blockquote:{paddingInline:"0.6em 0",paddingBlock:0,borderInlineStart:"4px solid rgba(100, 100, 100, 0.2)",opacity:.85}}},Z=function(B){var H,Se=B.componentCls,we=(0,N.e5)(B),ee=we.inputPaddingVertical+1;return{"&-edit-content":(H={position:"relative","div&":{insetInlineStart:-B.paddingSM,marginTop:-ee,marginBottom:"calc(1em - "+ee+"px)"}},(0,r.Z)(H,Se+"-edit-content-confirm",{position:"absolute",insetInlineEnd:B.marginXS+2,insetBlockEnd:B.marginXS,color:B.colorTextDescription,fontWeight:"normal",fontSize:B.fontSize,fontStyle:"normal",pointerEvents:"none"}),(0,r.Z)(H,"textarea",{margin:"0!important",MozTransition:"none",height:"1em"}),H)}},pe=function(B){return{"&-copy-success":(0,r.Z)({},` + &, + &:hover, + &:focus`,{color:B.colorSuccess})}},L=function(){var B;return B={},(0,r.Z)(B,` + a&-ellipsis, + span&-ellipsis + `,{display:"inline-block",maxWidth:"100%"}),(0,r.Z)(B,"&-single-line",{whiteSpace:"nowrap"}),(0,r.Z)(B,"&-ellipsis-single-line",{overflow:"hidden",textOverflow:"ellipsis","a&, span&":{verticalAlign:"bottom"}}),(0,r.Z)(B,"&-ellipsis-multiple-line",{display:"-webkit-box",overflow:"hidden",WebkitLineClamp:3,WebkitBoxOrient:"vertical"}),B},ye=function(B){var H,Se=B.componentCls,we=B.sizeMarginHeadingVerticalStart;return(0,r.Z)({},Se,(0,a.Z)((0,a.Z)((0,a.Z)((0,a.Z)((0,a.Z)((0,a.Z)((0,a.Z)((0,a.Z)((0,a.Z)((0,r.Z)({color:B.colorText,wordBreak:"break-word",lineHeight:B.lineHeight,"&&-secondary":{color:B.colorTextDescription},"&&-success":{color:B.colorSuccess},"&&-warning":{color:B.colorWarning},"&&-danger":{color:B.colorError,"a&:active, a&:focus":{color:B.colorErrorActive},"a&:hover":{color:B.colorErrorHover}},"&&-disabled":{color:B.colorTextDisabled,cursor:"not-allowed",userSelect:"none"}},` + div&, + p + `,{marginBottom:"1em"}),j(B)),(H={},(0,r.Z)(H,` + & + h1&, + & + h2&, + & + h3&, + & + h4&, + & + h5& + `,{marginTop:we}),(0,r.Z)(H,` + div, + ul, + li, + p, + h1, + h2, + h3, + h4, + h5`,(0,r.Z)({},` + + h1, + + h2, + + h3, + + h4, + + h5 + `,{marginTop:we})),H)),h()),T(B)),(0,r.Z)({},` + `+Se+`-expand, + `+Se+`-edit, + `+Se+`-copy + `,(0,a.Z)((0,a.Z)({},(0,$.N)(B)),{marginInlineStart:B.marginXXS}))),Z(B)),pe(B)),L()),{"&-rtl":{direction:"rtl"}}))},Ce=(0,F.Z)("Typography",function(je){return[ye(je)]},{sizeMarginHeadingVerticalStart:"1.2em",sizeMarginHeadingVerticalEnd:"0.5em"})},20640:function(Cn,ve,e){"use strict";var r=e(11742),a={"text/plain":"Text","text/html":"Url",default:"Text"},F="Copy to clipboard: #{key}, Enter";function C($){var t=(/mac os x/i.test(navigator.userAgent)?"\u2318":"Ctrl")+"+C";return $.replace(/#{\s*key\s*}/g,t)}function N($,t){var j,T,h,Z,pe,L,ye=!1;t||(t={}),j=t.debug||!1;try{h=r(),Z=document.createRange(),pe=document.getSelection(),L=document.createElement("span"),L.textContent=$,L.ariaHidden="true",L.style.all="unset",L.style.position="fixed",L.style.top=0,L.style.clip="rect(0, 0, 0, 0)",L.style.whiteSpace="pre",L.style.webkitUserSelect="text",L.style.MozUserSelect="text",L.style.msUserSelect="text",L.style.userSelect="text",L.addEventListener("copy",function(je){if(je.stopPropagation(),t.format)if(je.preventDefault(),typeof je.clipboardData=="undefined"){j&&console.warn("unable to use e.clipboardData"),j&&console.warn("trying IE specific stuff"),window.clipboardData.clearData();var B=a[t.format]||a.default;window.clipboardData.setData(B,$)}else je.clipboardData.clearData(),je.clipboardData.setData(t.format,$);t.onCopy&&(je.preventDefault(),t.onCopy(je.clipboardData))}),document.body.appendChild(L),Z.selectNodeContents(L),pe.addRange(Z);var Ce=document.execCommand("copy");if(!Ce)throw new Error("copy command was unsuccessful");ye=!0}catch(je){j&&console.error("unable to copy using execCommand: ",je),j&&console.warn("trying IE specific stuff");try{window.clipboardData.setData(t.format||"text",$),t.onCopy&&t.onCopy(window.clipboardData),ye=!0}catch(B){j&&console.error("unable to copy using clipboardData: ",B),j&&console.error("falling back to prompt"),T=C("message"in t?t.message:F),window.prompt(T,$)}}finally{pe&&(typeof pe.removeRange=="function"?pe.removeRange(Z):pe.removeAllRanges()),L&&document.body.removeChild(L),h()}return ye}Cn.exports=N},72378:function(Cn,ve,e){Cn=e.nmd(Cn);var r=200,a="__lodash_hash_undefined__",F=800,C=16,N=9007199254740991,$="[object Arguments]",t="[object Array]",j="[object AsyncFunction]",T="[object Boolean]",h="[object Date]",Z="[object Error]",pe="[object Function]",L="[object GeneratorFunction]",ye="[object Map]",Ce="[object Number]",je="[object Null]",B="[object Object]",H="[object Proxy]",Se="[object RegExp]",we="[object Set]",ee="[object String]",M="[object Undefined]",Ie="[object WeakMap]",ue="[object ArrayBuffer]",Ee="[object DataView]",_e="[object Float32Array]",xt="[object Float64Array]",be="[object Int8Array]",Nn="[object Int16Array]",ge="[object Int32Array]",me="[object Uint8Array]",Oe="[object Uint8ClampedArray]",dt="[object Uint16Array]",Te="[object Uint32Array]",ke=/[\\^$.*+?()[\]{}|]/g,Kt=/^\[object .+?Constructor\]$/,Pn=/^(?:0|[1-9]\d*)$/,qe={};qe[_e]=qe[xt]=qe[be]=qe[Nn]=qe[ge]=qe[me]=qe[Oe]=qe[dt]=qe[Te]=!0,qe[$]=qe[t]=qe[ue]=qe[T]=qe[Ee]=qe[h]=qe[Z]=qe[pe]=qe[ye]=qe[Ce]=qe[B]=qe[Se]=qe[we]=qe[ee]=qe[Ie]=!1;var A=typeof e.g=="object"&&e.g&&e.g.Object===Object&&e.g,pt=typeof self=="object"&&self&&self.Object===Object&&self,Zt=A||pt||Function("return this")(),Lt=ve&&!ve.nodeType&&ve,Wt=Lt&&!0&&Cn&&!Cn.nodeType&&Cn,$t=Wt&&Wt.exports===Lt,Qt=$t&&A.process,mn=function(){try{var i=Wt&&Wt.require&&Wt.require("util").types;return i||Qt&&Qt.binding&&Qt.binding("util")}catch(x){}}(),le=mn&&mn.isTypedArray;function We(i,x,O){switch(O.length){case 0:return i.call(x);case 1:return i.call(x,O[0]);case 2:return i.call(x,O[0],O[1]);case 3:return i.call(x,O[0],O[1],O[2])}return i.apply(x,O)}function He(i,x){for(var O=-1,tt=Array(i);++O-1}function g(i,x){var O=this.__data__,tt=Le(O,i);return tt<0?(++this.size,O.push([i,x])):O[tt][1]=x,this}b.prototype.clear=d,b.prototype.delete=o,b.prototype.get=f,b.prototype.has=v,b.prototype.set=g;function D(i){var x=-1,O=i==null?0:i.length;for(this.clear();++x1?O[Rn-1]:void 0,Wn=Rn>2?O[2]:void 0;for(pn=i.length>3&&typeof pn=="function"?(Rn--,pn):void 0,Wn&&tn(O[0],O[1],Wn)&&(pn=Rn<3?void 0:pn,Rn=1),x=Object(x);++tt-1&&i%1==0&&i0){if(++x>=F)return arguments[0]}else x=0;return i.apply(void 0,arguments)}}function I(i){if(i!=null){try{return Be.call(i)}catch(x){}try{return i+""}catch(x){}}return""}function E(i,x){return i===x||i!==i&&x!==x}var se=Pt(function(){return arguments}())?Pt:function(i){return wn(i)&&ce.call(i,"callee")&&!bn.call(i,"callee")},J=Array.isArray;function _(i){return i!=null&&Yt(i.length)&&!rt(i)}function Ct(i){return wn(i)&&_(i)}var jt=Re||dr;function rt(i){if(!sn(i))return!1;var x=ht(i);return x==pe||x==L||x==j||x==H}function Yt(i){return typeof i=="number"&&i>-1&&i%1==0&&i<=N}function sn(i){var x=typeof i;return i!=null&&(x=="object"||x=="function")}function wn(i){return i!=null&&typeof i=="object"}function En(i){if(!wn(i)||ht(i)!=B)return!1;var x=qt(i);if(x===null)return!0;var O=ce.call(x,"constructor")&&x.constructor;return typeof O=="function"&&O instanceof O&&Be.call(O)==Ot}var dn=le?Rt(le):en;function On(i){return Tn(i,Fn(i))}function Fn(i){return _(i)?at(i,!0):Bt(i)}var Ln=An(function(i,x,O){an(i,x,O)});function Jn(i){return function(){return i}}function tr(i){return i}function dr(){return!1}Cn.exports=Ln},13283:function(Cn,ve,e){"use strict";var r=e(87462),a=e(4942),F=e(1413),C=e(91),N=e(97685),$=e(71002),t=e(67294),j=e(94184),T=e.n(j),h=e(96774),Z=e.n(h),pe=e(42550),L=e(38299),ye=e(72659),Ce=e(22342),je=e(8806),B=e(80334),H=e(43083),Se=e(75961),we=["colSpan","rowSpan","style","className"];function ee(ge,me,Oe,dt){var Te=ge+me-1;return ge<=dt&&Te>=Oe}function M(ge){return ge&&(0,$.Z)(ge)==="object"&&!Array.isArray(ge)&&!t.isValidElement(ge)}function Ie(ge){return typeof ge=="string"?!0:(0,pe.Yr)(ge)}var ue=function(me){var Oe=me.ellipsis,dt=me.rowType,Te=me.children,ke,Kt=Oe===!0?{showTitle:!0}:Oe;return Kt&&(Kt.showTitle||dt==="header")&&(typeof Te=="string"||typeof Te=="number"?ke=Te.toString():t.isValidElement(Te)&&typeof Te.props.children=="string"&&(ke=Te.props.children)),ke};function Ee(ge,me){var Oe,dt,Te,ke=ge.prefixCls,Kt=ge.className,Pn=ge.record,qe=ge.index,A=ge.renderIndex,pt=ge.dataIndex,Zt=ge.render,Lt=ge.children,Wt=ge.component,$t=Wt===void 0?"td":Wt,Qt=ge.colSpan,mn=ge.rowSpan,le=ge.fixLeft,We=ge.fixRight,He=ge.firstFixLeft,Rt=ge.lastFixLeft,zt=ge.firstFixRight,wt=ge.lastFixRight,Xe=ge.appendNode,ft=ge.additionalProps,gt=ft===void 0?{}:ft,_t=ge.ellipsis,Be=ge.align,ce=ge.rowType,Ge=ge.isSticky,Ye=ge.hovering,Ot=ge.onHover,z="".concat(ke,"-cell"),V=t.useContext(H.Z),k=t.useContext(ye.Z),Ke=t.useContext(je.Z),vt=Ke.allColumnsFixedLeft,qt=t.useMemo(function(){if((0,L.ik)(Lt))return[Lt];var f=(0,L.aM)(Pn,pt),v=f,g=void 0;if(Zt){var D=Zt(f,Pn,A);M(D)?(v=D.children,g=D.props,V.renderWithProps=!0):v=D}return[v,g]},[V.renderWithProps?Math.random():0,Lt,pt,V,Pn,Zt,A]),Sn=(0,N.Z)(qt,2),bn=Sn[0],Un=Sn[1],Dn=bn;(0,$.Z)(Dn)==="object"&&!Array.isArray(Dn)&&!t.isValidElement(Dn)&&(Dn=null),_t&&(Rt||zt)&&(Dn=t.createElement("span",{className:"".concat(z,"-content")},Dn));var $e=Un||{},Re=$e.colSpan,De=$e.rowSpan,Je=$e.style,mt=$e.className,Ut=(0,C.Z)($e,we),Vt=(Oe=Re!==void 0?Re:Qt)!==null&&Oe!==void 0?Oe:1,Ze=(dt=De!==void 0?De:mn)!==null&&dt!==void 0?dt:1;if(Vt===0||Ze===0)return null;var xe={},he=typeof le=="number"&&k,oe=typeof We=="number"&&k;he&&(xe.position="sticky",xe.left=le),oe&&(xe.position="sticky",xe.right=We);var Dt={};Be&&(Dt.textAlign=Be);var Kn=function(v){var g;Pn&&Ot(qe,qe+Ze-1),gt==null||(g=gt.onMouseEnter)===null||g===void 0||g.call(gt,v)},b=function(v){var g;Pn&&Ot(-1,-1),gt==null||(g=gt.onMouseLeave)===null||g===void 0||g.call(gt,v)},d=ue({rowType:ce,ellipsis:_t,children:bn}),o=(0,F.Z)((0,F.Z)((0,F.Z)({title:d},Ut),gt),{},{colSpan:Vt!==1?Vt:null,rowSpan:Ze!==1?Ze:null,className:T()(z,Kt,(Te={},(0,a.Z)(Te,"".concat(z,"-fix-left"),he&&k),(0,a.Z)(Te,"".concat(z,"-fix-left-first"),He&&k),(0,a.Z)(Te,"".concat(z,"-fix-left-last"),Rt&&k),(0,a.Z)(Te,"".concat(z,"-fix-left-all"),Rt&&vt&&k),(0,a.Z)(Te,"".concat(z,"-fix-right"),oe&&k),(0,a.Z)(Te,"".concat(z,"-fix-right-first"),zt&&k),(0,a.Z)(Te,"".concat(z,"-fix-right-last"),wt&&k),(0,a.Z)(Te,"".concat(z,"-ellipsis"),_t),(0,a.Z)(Te,"".concat(z,"-with-append"),Xe),(0,a.Z)(Te,"".concat(z,"-fix-sticky"),(he||oe)&&Ge&&k),(0,a.Z)(Te,"".concat(z,"-row-hover"),!Un&&Ye),Te),gt.className,mt),style:(0,F.Z)((0,F.Z)((0,F.Z)((0,F.Z)({},gt.style),Dt),xe),Je),onMouseEnter:Kn,onMouseLeave:b,ref:Ie($t)?me:null});return t.createElement($t,o,Xe,Dn)}var _e=t.forwardRef(Ee);_e.displayName="Cell";var xt=["expanded","className","hovering"],be=t.memo(_e,function(ge,me){return me.shouldCellUpdate?xt.every(function(Oe){return ge[Oe]===me[Oe]})&&!me.shouldCellUpdate(me.record,ge.record):Z()(ge,me)}),Nn=t.forwardRef(function(ge,me){var Oe=ge.index,dt=ge.additionalProps,Te=dt===void 0?{}:dt,ke=ge.colSpan,Kt=ge.rowSpan,Pn=Te.colSpan,qe=Te.rowSpan,A=ke!=null?ke:Pn,pt=Kt!=null?Kt:qe,Zt=(0,Se.S)(Ce.Z,function($t){var Qt=ee(Oe,pt||1,$t==null?void 0:$t.startRow,$t==null?void 0:$t.endRow);return{onHover:$t==null?void 0:$t.onHover,hovering:Qt}}),Lt=Zt.onHover,Wt=Zt.hovering;return t.createElement(be,(0,r.Z)({},ge,{colSpan:A,rowSpan:pt,hovering:Wt,ref:me,onHover:Lt}))});Nn.displayName="WrappedCell",ve.Z=Nn},75961:function(Cn,ve,e){"use strict";e.d(ve,{S:function(){return j},k:function(){return t}});var r=e(97685),a=e(67294),F=e(8410),C=e(66680),N=e(96774),$=e.n(N);function t(){var T=a.createContext(null),h=function(pe){var L=pe.value,ye=pe.children,Ce=a.useRef(L);Ce.current=L;var je=a.useState(function(){return{getValue:function(){return Ce.current},listeners:new Set}}),B=(0,r.Z)(je,1),H=B[0];return(0,F.Z)(function(){H.listeners.forEach(function(Se){Se(L)})},[L]),a.createElement(T.Provider,{value:H},ye)};return{Context:T,Provider:h}}function j(T,h){var Z=(0,C.Z)(h),pe=a.useContext(T==null?void 0:T.Context),L=pe||{},ye=L.listeners,Ce=L.getValue,je=a.useState(function(){return Z(pe?Ce():null)}),B=(0,r.Z)(je,2),H=B[0],Se=B[1];return(0,F.Z)(function(){if(!pe)return;function we(ee){Se(function(M){var Ie=Z(ee);return $()(M,Ie)?M:Ie})}return ye.add(we),function(){ye.delete(we)}},[pe]),H}},18339:function(Cn,ve,e){"use strict";e.d(ve,{Z:function(){return pe}});var r=e(87462),a=e(67294),F=e(98876),C=e(13283),N=e(88946),$=e(3991);function t(L){var ye=L.className,Ce=L.index,je=L.children,B=L.colSpan,H=B===void 0?1:B,Se=L.rowSpan,we=L.align,ee=a.useContext(N.Z),M=ee.prefixCls,Ie=ee.direction,ue=a.useContext(F.Z),Ee=ue.scrollColumnIndex,_e=ue.stickyOffsets,xt=ue.flattenColumns,be=Ce+H-1,Nn=be+1===Ee?H+1:H,ge=(0,$.v)(Ce,Ce+Nn-1,xt,_e,Ie);return a.createElement(C.Z,(0,r.Z)({className:ye,index:Ce,component:"td",prefixCls:M,record:null,dataIndex:null,align:we,colSpan:Nn,rowSpan:Se,render:function(){return je}},ge))}var j=e(91),T=["children"];function h(L){var ye=L.children,Ce=(0,j.Z)(L,T);return a.createElement("tr",Ce,ye)}function Z(L){var ye=L.children;return ye}Z.Row=h,Z.Cell=t;var pe=Z},98876:function(Cn,ve,e){"use strict";var r=e(67294),a=r.createContext({});ve.Z=a},80540:function(Cn,ve,e){"use strict";e.d(ve,{c:function(){return $}});var r=e(67294),a=e(88946),F=e(18339),C=e(98876);function N(t){var j=t.children,T=t.stickyOffsets,h=t.flattenColumns,Z=r.useContext(a.Z),pe=Z.prefixCls,L=h.length-1,ye=h[L],Ce=r.useMemo(function(){return{stickyOffsets:T,flattenColumns:h,scrollColumnIndex:ye!=null&&ye.scrollbar?L:null}},[ye,h,L,T]);return r.createElement(C.Z.Provider,{value:Ce},r.createElement("tfoot",{className:"".concat(pe,"-summary")},j))}ve.Z=N;var $=F.Z},3705:function(Cn,ve,e){"use strict";e.d(ve,{R:function(){return he},Z:function(){return Kn}});var r=e(4942),a=e(87462),F=e(1413),C=e(74902),N=e(97685),$=e(71002),t=e(67294),j=e(5110),T=e(64217),h=e(79370),Z=e(94184),pe=e.n(Z),L=e(96774),ye=e.n(L),Ce=e(80334),je=e(48555),B=e(74204);function H(b){return null}var Se=H;function we(b){return null}var ee=we,M=e(13283),Ie=e(88946),ue=e(3991),Ee=e(38299);function _e(b){var d=b.cells,o=b.stickyOffsets,f=b.flattenColumns,v=b.rowComponent,g=b.cellComponent,D=b.onHeaderRow,P=b.index,U=t.useContext(Ie.Z),fe=U.prefixCls,X=U.direction,w;D&&(w=D(d.map(function(G){return G.column}),P));var l=(0,Ee.cz)(d.map(function(G){return G.column}));return t.createElement(v,w,d.map(function(G,ne){var q=G.column,Y=(0,ue.v)(G.colStart,G.colEnd,f,o,X),ot;return q&&q.onHeaderCell&&(ot=G.column.onHeaderCell(q)),t.createElement(M.Z,(0,a.Z)({},G,{ellipsis:q.ellipsis,align:q.align,component:g,prefixCls:fe,key:l[ne]},Y,{additionalProps:ot,rowType:"header"}))}))}_e.displayName="HeaderRow";var xt=_e;function be(b){var d=[];function o(D,P){var U=arguments.length>2&&arguments[2]!==void 0?arguments[2]:0;d[U]=d[U]||[];var fe=P,X=D.filter(Boolean).map(function(w){var l={key:w.key,className:w.className||"",children:w.title,column:w,colStart:fe},G=1,ne=w.children;return ne&&ne.length>0&&(G=o(ne,fe,U+1).reduce(function(q,Y){return q+Y},0),l.hasSubColumns=!0),"colSpan"in w&&(G=w.colSpan),"rowSpan"in w&&(l.rowSpan=w.rowSpan),l.colSpan=G,l.colEnd=l.colStart+G-1,d[U].push(l),fe+=G,G});return X}o(b,0);for(var f=d.length,v=function(P){d[P].forEach(function(U){!("rowSpan"in U)&&!U.hasSubColumns&&(U.rowSpan=f-P)})},g=0;g1?m-1:0),R=1;R=0;P-=1){var U=d[P],fe=o&&o[P],X=fe&&fe[gt.v];if(U||X||D){var w=X||{},l=w.columnType,G=(0,ft.Z)(w,_t);v.unshift(t.createElement("col",(0,a.Z)({key:P,style:{width:U}},G))),D=!0}}return t.createElement("colgroup",null,v)}var ce=Be;function Ge(b){var d=b.className,o=b.children;return t.createElement("div",{className:d},o)}var Ye=Ge,Ot=e(80540);function z(b){var d,o=b.prefixCls,f=b.record,v=b.onExpand,g=b.expanded,D=b.expandable,P="".concat(o,"-row-expand-icon");if(!D)return t.createElement("span",{className:pe()(P,"".concat(o,"-row-spaced"))});var U=function(X){v(f,X),X.stopPropagation()};return t.createElement("span",{className:pe()(P,(d={},(0,r.Z)(d,"".concat(o,"-row-expanded"),g),(0,r.Z)(d,"".concat(o,"-row-collapsed"),!g),d)),onClick:U})}function V(b,d,o){var f=[];function v(g){(g||[]).forEach(function(D,P){f.push(d(D,P)),v(D[o])})}return v(b),f}var k=e(64019),Ke=e(27678),vt=function(d,o){var f,v,g=d.scrollBodyRef,D=d.onScroll,P=d.offsetScroll,U=d.container,fe=t.useContext(Ie.Z),X=fe.prefixCls,w=((f=g.current)===null||f===void 0?void 0:f.scrollWidth)||0,l=((v=g.current)===null||v===void 0?void 0:v.clientWidth)||0,G=w&&l*(l/w),ne=t.useRef(),q=Rt({scrollLeft:0,isHiddenScrollBar:!1}),Y=(0,N.Z)(q,2),ot=Y[0],at=Y[1],ct=t.useRef({delta:0,x:0}),Qe=t.useState(!1),Le=(0,N.Z)(Qe,2),Ue=Le[0],ut=Le[1],ht=function(){ut(!1)},Pt=function(Nt){Nt.persist(),ct.current.delta=Nt.pageX-ot.scrollLeft,ct.current.x=0,ut(!0),Nt.preventDefault()},yt=function(Nt){var Xt,on=Nt||((Xt=window)===null||Xt===void 0?void 0:Xt.event),it=on.buttons;if(!Ue||it===0){Ue&&ut(!1);return}var Ne=ct.current.x+Nt.pageX-ct.current.x-ct.current.delta;Ne<=0&&(Ne=0),Ne+G>=l&&(Ne=l-G),D({scrollLeft:Ne/l*(w+2)}),ct.current.x=Nt.pageX},en=function(){if(!!g.current){var Nt=(0,Ke.os)(g.current).top,Xt=Nt+g.current.offsetHeight,on=U===window?document.documentElement.scrollTop+window.innerHeight:(0,Ke.os)(U).top+U.clientHeight;Xt-(0,B.Z)()<=on||Nt>=on-P?at(function(it){return(0,F.Z)((0,F.Z)({},it),{},{isHiddenScrollBar:!0})}):at(function(it){return(0,F.Z)((0,F.Z)({},it),{},{isHiddenScrollBar:!1})})}},Bt=function(Nt){at(function(Xt){return(0,F.Z)((0,F.Z)({},Xt),{},{scrollLeft:Nt/w*l||0})})};return t.useImperativeHandle(o,function(){return{setScrollLeft:Bt}}),t.useEffect(function(){var an=(0,k.Z)(document.body,"mouseup",ht,!1),Nt=(0,k.Z)(document.body,"mousemove",yt,!1);return en(),function(){an.remove(),Nt.remove()}},[G,Ue]),t.useEffect(function(){var an=(0,k.Z)(U,"scroll",en,!1),Nt=(0,k.Z)(window,"resize",en,!1);return function(){an.remove(),Nt.remove()}},[U]),t.useEffect(function(){ot.isHiddenScrollBar||at(function(an){var Nt=g.current;return Nt?(0,F.Z)((0,F.Z)({},an),{},{scrollLeft:Nt.scrollLeft/Nt.scrollWidth*Nt.clientWidth}):an})},[ot.isHiddenScrollBar]),w<=l||!G||ot.isHiddenScrollBar?null:t.createElement("div",{style:{height:(0,B.Z)(),width:l,bottom:P},className:"".concat(X,"-sticky-scroll")},t.createElement("div",{onMouseDown:Pt,ref:ne,className:pe()("".concat(X,"-sticky-scroll-bar"),(0,r.Z)({},"".concat(X,"-sticky-scroll-bar-active"),Ue)),style:{width:"".concat(G,"px"),transform:"translate3d(".concat(ot.scrollLeft,"px, 0, 0)")}}))},qt=t.forwardRef(vt),Sn=e(98924),bn=(0,Sn.Z)()?window:null;function Un(b,d){var o=(0,$.Z)(b)==="object"?b:{},f=o.offsetHeader,v=f===void 0?0:f,g=o.offsetSummary,D=g===void 0?0:g,P=o.offsetScroll,U=P===void 0?0:P,fe=o.getContainer,X=fe===void 0?function(){return bn}:fe,w=X()||bn;return t.useMemo(function(){var l=!!b;return{isSticky:l,stickyClassName:l?"".concat(d,"-sticky-holder"):"",offsetHeader:v,offsetSummary:D,offsetScroll:U,container:w}},[U,v,D,d,w])}var Dn=e(42550),$e=["className","noData","columns","flattenColumns","colWidths","columCount","stickyOffsets","direction","fixHeader","stickyTopOffset","stickyBottomOffset","stickyClassName","onScroll","maxContentScroll","children"];function Re(b,d){return(0,t.useMemo)(function(){for(var o=[],f=0;f=0})},[g]),yt=g[g.length-1],en={fixed:yt?yt.fixed:null,scrollbar:!0,onHeaderCell:function(){return{className:"".concat(ct,"-cell-scrollbar")}}},Bt=(0,t.useMemo)(function(){return Ue?[].concat((0,C.Z)(v),[en]):v},[Ue,v]),an=(0,t.useMemo)(function(){return Ue?[].concat((0,C.Z)(g),[en]):g},[Ue,g]),Nt=(0,t.useMemo)(function(){var on=U.right,it=U.left;return(0,F.Z)((0,F.Z)({},U),{},{left:fe==="rtl"?[].concat((0,C.Z)(it.map(function(Ne){return Ne+Ue})),[0]):it,right:fe==="rtl"?on:[].concat((0,C.Z)(on.map(function(Ne){return Ne+Ue})),[0]),isSticky:Le})},[Ue,U,Le]),Xt=Re(D,P);return t.createElement("div",{style:(0,F.Z)({overflow:"hidden"},Le?{top:w,bottom:l}:{}),ref:ht,className:pe()(o,(0,r.Z)({},G,!!G))},t.createElement("table",{style:{tableLayout:"fixed",visibility:f||Xt?null:"hidden"}},(!f||!q||Pt)&&t.createElement(ce,{colWidths:Xt?[].concat((0,C.Z)(Xt),[Ue]):[],columCount:P+1,columns:an}),Y((0,F.Z)((0,F.Z)({},ot),{},{stickyOffsets:Nt,columns:Bt,flattenColumns:an}))))});De.displayName="FixedHolder";var Je=De,mt=e(18339),Ut=e(72659),Vt=e(45233),Ze=[],xe={},he="rc-table-internal-hook",oe=t.memo(function(b){var d=b.children;return d},function(b,d){return ye()(b.props,d.props)?b.pingLeft!==d.pingLeft||b.pingRight!==d.pingRight:!1});function Dt(b){var d,o=b.prefixCls,f=b.className,v=b.rowClassName,g=b.style,D=b.data,P=b.rowKey,U=b.scroll,fe=b.tableLayout,X=b.direction,w=b.title,l=b.footer,G=b.summary,ne=b.id,q=b.showHeader,Y=b.components,ot=b.emptyText,at=b.onRow,ct=b.onHeaderRow,Qe=b.internalHooks,Le=b.transformColumns,Ue=b.internalRefs,ut=b.sticky,ht=D||Ze,Pt=!!ht.length,yt=t.useCallback(function(cn,kt){return(0,Ee.aM)(Y||{},cn)||kt},[Y]),en=t.useMemo(function(){return typeof P=="function"?P:function(cn){var kt=cn&&cn[P];return kt}},[P]),Bt=(0,gt.g)(b),an=Bt.expandIcon,Nt=Bt.expandedRowKeys,Xt=Bt.defaultExpandedRowKeys,on=Bt.defaultExpandAllRows,it=Bt.expandedRowRender,Ne=Bt.columnTitle,et=Bt.onExpand,un=Bt.onExpandedRowsChange,Tn=Bt.expandRowByClick,An=Bt.rowExpandable,ir=Bt.expandIconColumnIndex,Yn=Bt.expandedRowClassName,Mn=Bt.childrenColumnName,Me=Bt.indentSize,bt=an||z,Zn=Mn||"children",tn=t.useMemo(function(){return it?"row":b.expandable&&Qe===he&&b.expandable.__PARENT_RENDER_ICON__||ht.some(function(cn){return cn&&(0,$.Z)(cn)==="object"&&cn[Zn]})?"nest":!1},[!!it,ht]),Xn=t.useState(function(){return Xt||(on?V(ht,en,Zn):[])}),In=(0,N.Z)(Xn,2),zn=In[0],Qn=In[1],qn=t.useMemo(function(){return new Set(Nt||zn||[])},[Nt,zn]),m=t.useCallback(function(cn){var kt=en(cn,ht.indexOf(cn)),nr,fr=qn.has(kt);fr?(qn.delete(kt),nr=(0,C.Z)(qn)):nr=[].concat((0,C.Z)(qn),[kt]),Qn(nr),et&&et(!fr,cn),un&&un(nr)},[en,qn,ht,et,un]),s=t.useState(0),R=(0,N.Z)(s,2),y=R[0],I=R[1],E=(0,He.Z)((0,F.Z)((0,F.Z)((0,F.Z)({},b),Bt),{},{expandable:!!it,columnTitle:Ne,expandedKeys:qn,getRowKey:en,onTriggerExpand:m,expandIcon:bt,expandIconColumnIndex:ir,direction:X}),Qe===he?Le:null),se=(0,N.Z)(E,2),J=se[0],_=se[1],Ct=t.useMemo(function(){return{columns:J,flattenColumns:_}},[J,_]),jt=t.useRef(),rt=t.useRef(),Yt=t.useRef(),sn=t.useRef(),wn=t.useRef(),En=t.useState(!1),dn=(0,N.Z)(En,2),On=dn[0],Fn=dn[1],Ln=t.useState(!1),Jn=(0,N.Z)(Ln,2),tr=Jn[0],dr=Jn[1],i=Rt(new Map),x=(0,N.Z)(i,2),O=x[0],tt=x[1],Rn=(0,Ee.cz)(_),pn=Rn.map(function(cn){return O.get(cn)}),Wn=t.useMemo(function(){return pn},[pn.join("_")]),hn=Xe(Wn,_.length,X),Ve=U&&(0,Ee.ik)(U.y),Ae=U&&(0,Ee.ik)(U.x)||Boolean(Bt.fixed),u=Ae&&_.some(function(cn){var kt=cn.fixed;return kt}),n=t.useRef(),p=Un(ut,o),c=p.isSticky,S=p.offsetHeader,K=p.offsetSummary,ze=p.offsetScroll,W=p.stickyClassName,Q=p.container,ae=G==null?void 0:G(ht),nt=(Ve||c)&&t.isValidElement(ae)&&ae.type===mt.Z&&ae.props.fixed,st,ie,de;Ve&&(ie={overflowY:"scroll",maxHeight:U.y}),Ae&&(st={overflowX:"auto"},Ve||(ie={overflowY:"hidden"}),de={width:(U==null?void 0:U.x)===!0?"auto":U==null?void 0:U.x,minWidth:"100%"});var Fe=t.useCallback(function(cn,kt){(0,j.Z)(jt.current)&&tt(function(nr){if(nr.get(cn)!==kt){var fr=new Map(nr);return fr.set(cn,kt),fr}return nr})},[]),Ft=zt(null),re=(0,N.Z)(Ft,2),Tt=re[0],ln=re[1];function Gt(cn,kt){!kt||(typeof kt=="function"?kt(cn):kt.scrollLeft!==cn&&(kt.scrollLeft=cn))}var At=function(kt){var nr=kt.currentTarget,fr=kt.scrollLeft,xr=X==="rtl",gr=typeof fr=="number"?fr:nr.scrollLeft,Sr=nr||xe;if(!ln()||ln()===Sr){var Zr;Tt(Sr),Gt(gr,rt.current),Gt(gr,Yt.current),Gt(gr,wn.current),Gt(gr,(Zr=n.current)===null||Zr===void 0?void 0:Zr.setScrollLeft)}if(nr){var yr=nr.scrollWidth,Ir=nr.clientWidth;if(yr===Ir){Fn(!1),dr(!1);return}xr?(Fn(-gr0)):(Fn(gr>0),dr(gr0?[].concat((0,a.Z)(H),(0,a.Z)(L(M).map(function(Ie){return(0,F.Z)({fixed:ee},Ie)}))):[].concat((0,a.Z)(H),[(0,F.Z)((0,F.Z)({},Se),{},{fixed:ee})])},[])}function ye(B){for(var H=!0,Se=0;Se=0;M-=1){var Ie=B[M];if(ee&&Ie.fixed!=="right")ee=!1;else if(!ee&&Ie.fixed==="right"){warning(!1,"Index ".concat(M+1," of `columns` missing `fixed='right'` prop."));break}}}function Ce(B){return B.map(function(H){var Se=H.fixed,we=(0,C.Z)(H,Z),ee=Se;return Se==="left"?ee="right":Se==="right"&&(ee="left"),(0,F.Z)({fixed:ee},we)})}function je(B,H){var Se=B.prefixCls,we=B.columns,ee=B.children,M=B.expandable,Ie=B.expandedKeys,ue=B.columnTitle,Ee=B.getRowKey,_e=B.onTriggerExpand,xt=B.expandIcon,be=B.rowExpandable,Nn=B.expandIconColumnIndex,ge=B.direction,me=B.expandRowByClick,Oe=B.columnWidth,dt=B.fixed,Te=N.useMemo(function(){return we||pe(ee)},[we,ee]),ke=N.useMemo(function(){if(M){var qe,A=Te.slice();if(!A.includes(T.w)){var pt=Nn||0;pt>=0&&A.splice(pt,0,T.w)}var Zt=A.indexOf(T.w);A=A.filter(function(Qt,mn){return Qt!==T.w||mn===Zt});var Lt=Te[Zt],Wt;(dt==="left"||dt)&&!Nn?Wt="left":(dt==="right"||dt)&&Nn===Te.length?Wt="right":Wt=Lt?Lt.fixed:null;var $t=(qe={},(0,r.Z)(qe,j.v,{className:"".concat(Se,"-expand-icon-col"),columnType:"EXPAND_COLUMN"}),(0,r.Z)(qe,"title",ue),(0,r.Z)(qe,"fixed",Wt),(0,r.Z)(qe,"className","".concat(Se,"-row-expand-icon-cell")),(0,r.Z)(qe,"width",Oe),(0,r.Z)(qe,"render",function(mn,le,We){var He=Ee(le,We),Rt=Ie.has(He),zt=be?be(le):!0,wt=xt({prefixCls:Se,expanded:Rt,expandable:zt,record:le,onExpand:_e});return me?N.createElement("span",{onClick:function(ft){return ft.stopPropagation()}},wt):wt}),qe);return A.map(function(Qt){return Qt===T.w?$t:Qt})}return Te.filter(function(Qt){return Qt!==T.w})},[M,Te,Ee,Ie,xt,ge]),Kt=N.useMemo(function(){var qe=ke;return H&&(qe=H(qe)),qe.length||(qe=[{render:function(){return null}}]),qe},[H,ke,ge]),Pn=N.useMemo(function(){return ge==="rtl"?Ce(L(Kt)):L(Kt)},[Kt,ge]);return[Kt,Pn]}ve.Z=je},82327:function(Cn,ve,e){"use strict";e.d(ve,{ER:function(){return a.c},vP:function(){return F.v}});var r=e(3705),a=e(80540),F=e(62978);ve.ZP=r.Z},3991:function(Cn,ve,e){"use strict";e.d(ve,{v:function(){return r}});function r(a,F,C,N,$){var t=C[a]||{},j=C[F]||{},T,h;t.fixed==="left"?T=N.left[a]:j.fixed==="right"&&(h=N.right[F]);var Z=!1,pe=!1,L=!1,ye=!1,Ce=C[F+1],je=C[a-1];if($==="rtl"){if(T!==void 0){var B=je&&je.fixed==="left";ye=!B}else if(h!==void 0){var H=Ce&&Ce.fixed==="right";L=!H}}else if(T!==void 0){var Se=Ce&&Ce.fixed==="left";Z=!Se}else if(h!==void 0){var we=je&&je.fixed==="right";pe=!we}return{fixLeft:T,fixRight:h,lastFixLeft:Z,firstFixRight:pe,lastFixRight:L,firstFixLeft:ye,isSticky:N.isSticky}}},62978:function(Cn,ve,e){"use strict";e.d(ve,{g:function(){return $},v:function(){return N}});var r=e(1413),a=e(91),F=e(80334),C=["expandable"],N="RC_TABLE_INTERNAL_COL_DEFINE";function $(t){var j=t.expandable,T=(0,a.Z)(t,C),h;return"expandable"in t?h=(0,r.Z)((0,r.Z)({},T),j):h=T,h.showExpandColumn===!1&&(h.expandIconColumnIndex=-1),h}},38299:function(Cn,ve,e){"use strict";e.d(ve,{aM:function(){return F},cz:function(){return C},ik:function(){return N}});var r="RC_TABLE_KEY";function a($){return $==null?[]:Array.isArray($)?$:[$]}function F($,t){if(!t&&typeof t!="number")return $;for(var j=a(t),T=$,h=0;h. + */ +(self.webpackChunktopiam_console=self.webpackChunktopiam_console||[]).push([[211],{81618:function(dn){"use strict";(function(Ft,ot){dn.exports=ot()})(this,function(){function Ft(ct){var et=[];return ct.AMapUI&&et.push(ot(ct.AMapUI)),ct.Loca&&et.push(Lt(ct.Loca)),Promise.all(et)}function ot(ct){return new Promise(function(et,Ee){var it=[];if(ct.plugins)for(var st=0;st"),IRendererService:Symbol.for("IRendererService"),IShaderModuleService:Symbol.for("IShaderModuleService"),IIconService:Symbol.for("IIconService"),IFontService:Symbol.for("IFontService"),IInteractionService:Symbol.for("IInteractionService"),IPickingService:Symbol.for("IPickingService"),IControlService:Symbol.for("IControlService"),IStyleAttributeService:Symbol.for("IStyleAttributeService"),ILayer:Symbol.for("ILayer"),ILayerPlugin:Symbol.for("ILayerPlugin"),INormalPass:Symbol.for("INormalPass"),IPostProcessor:Symbol.for("IPostProcessor"),IPostProcessingPass:Symbol.for("IPostProcessingPass"),IFactoryPostProcessingPass:Symbol.for("Factory"),IFactoryNormalPass:Symbol.for("Factory"),IMultiPassRenderer:Symbol.for("IMultiPassRenderer"),SceneID:Symbol.for("SceneID"),MapConfig:Symbol.for("MapConfig")};function Ae(n){var l=131,a=137,s=0;n+="x";for(var o=Math.floor(9007199254740991/a),h=0;ho&&(s=Math.floor(s/a)),s=s*l+n.charCodeAt(h);return s}function be(n){n=n.toString();for(var l=5381,a=n.length;a;)l=l*33^n.charCodeAt(--a);return l>>>0}var me=ot(74902),ee=ot(96486),we="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";function _e(n){this.message=n}_e.prototype=new Error,_e.prototype.name="InvalidCharacterError";function gt(n){var l=String(n).replace(/=+$/,"");if(l.length%4===1)throw new _e("'atob' failed: The string to be decoded is not correctly encoded.");for(var a="",s=0,o,h,v=0;h=l.charAt(v++);~h&&(o=s%4?o*64+h:h,s++%4)?a+=String.fromCharCode(255&o>>(-2*s&6)):0)h=we.indexOf(h);return a}function Pt(n){n=String(n);for(var l,a,s,o,h="",v=0,m=n.length%3;v255||(s=n.charCodeAt(v++))>255||(o=n.charCodeAt(v++))>255)throw new TypeError("Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.");l=a<<16|s<<8|o,h+=we.charAt(l>>18&63)+we.charAt(l>>12&63)+we.charAt(l>>6&63)+we.charAt(l&63)}return m?h.slice(0,m-3)+"===".substring(m):h}var J=function(){function n(l,a){(0,ct.Z)(this,n),this.buffers=l,this.type=a}return(0,et.Z)(n,[{key:"arraybuffer",value:function(){return Promise.resolve(this.buffers[0])}},{key:"stream",value:function(){throw new Error("not implemented")}},{key:"text",value:function(){throw new Error("not implemented")}},{key:"slice",value:function(a,s,o){throw new Error("not implemented")}}]),n}(),vt,W=function(){return vt||(vt=typeof globalThis!="undefined"?globalThis:typeof self!="undefined"?self:typeof window!="undefined"?window:typeof ot.g!="undefined"?ot.g:{})};vt=W();var dt;typeof window=="undefined"?dt={devicePixelRatio:1,navigator:{userAgent:"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36",browserLanguage:"zh-CN",language:"zh-CN",platform:""},location:"",document:{documentElement:{style:{}},createEvent:function(){return!0},getElementById:function(l){return!0},createElement:function(){return{className:"",classList:{add:function(){return""}}}},createElementNS:function(){return!0},addEventListener:function(){return!0},removeEventListener:function(){return!0},querySelector:function(){return!1}},performance:{mark:function(l){return null},clearMeasures:function(l){return null},clearMarks:function(l){return null},measure:function(l,a,s){return{duration:0}},now:function(){return new Date().getTime()}},Blob:"",dispatchEvent:function(l){return!0},Event:function(l,a){return!0},createElement:function(){return!0},createElementNS:function(){return!0},XMLHttpRequest:function(){return!0},addEventListener:function(){return!0},removeEventListener:function(){return!0},requestAnimationFrame:function(){return!0},cancelAnimationFrame:function(){return!0},clearTimeout:function(){return!0}}:dt=window;var Vt=typeof my!="undefined"&&!!my&&typeof my.showToast=="function"&&my.isFRM!==!0,ne=Vt?my.getSystemInfoSync().pixelRatio:dt.devicePixelRatio,ge=(0,et.Z)(function n(l,a){(0,ct.Z)(this,n),this.cancelBubble=!1,this.cancelable=!1,this.target=null,this.currentTarget=null,this.preventDefault=function(){},this.stopPropagation=function(){},this.type=l,this.timeStamp=Date.now()});function $e(n,l){var a=typeof Symbol!="undefined"&&n[Symbol.iterator]||n["@@iterator"];if(!a){if(Array.isArray(n)||(a=jt(n))||l&&n&&typeof n.length=="number"){a&&(n=a);var s=0,o=function(){};return{s:o,n:function(){return s>=n.length?{done:!0}:{done:!1,value:n[s++]}},e:function(A){throw A},f:o}}throw new TypeError(`Invalid attempt to iterate non-iterable instance. +In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}var h=!0,v=!1,m;return{s:function(){a=a.call(n)},n:function(){var A=a.next();return h=A.done,A},e:function(A){v=!0,m=A},f:function(){try{!h&&a.return!=null&&a.return()}finally{if(v)throw m}}}}function jt(n,l){if(!!n){if(typeof n=="string")return Qt(n,l);var a=Object.prototype.toString.call(n).slice(8,-1);if(a==="Object"&&n.constructor&&(a=n.constructor.name),a==="Map"||a==="Set")return Array.from(n);if(a==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(a))return Qt(n,l)}}function Qt(n,l){(l==null||l>n.length)&&(l=n.length);for(var a=0,s=new Array(l);a2&&arguments[2]!==void 0?arguments[2]:{},h=Te.get(this);h||(h={}),h[a]||(h[a]=[]),h[a].push(s),Te.set(this,h),o.capture,o.once,o.passive}},{key:"removeEventListener",value:function(a,s){var o=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{},h=Te.get(this);if(h){var v=h[a];if(v&&v.length>0){for(var m=v.length;m--;m>0)if(v[m]===s){v.splice(m,1);break}}}}},{key:"dispatchEvent",value:function(a){var s=Te.get(this)[a.type];if(s){var o=$e(s),h;try{for(o.s();!(h=o.n()).done;){var v=h.value;v(a)}}catch(m){o.e(m)}finally{o.f()}}}}]),n}();function Ke(n){var l=sr();return function(){var s=(0,b.Z)(n),o;if(l){var h=(0,b.Z)(this).constructor;o=Reflect.construct(s,arguments,h)}else o=s.apply(this,arguments);return(0,P.Z)(this,o)}}function sr(){if(typeof Reflect=="undefined"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(n){return!1}}var ie=function(n){(0,Zt.Z)(a,n);var l=Ke(a);function a(){var s;return(0,ct.Z)(this,a),s=l.call(this),s.childNodes=[],s}return(0,et.Z)(a,[{key:"appendChild",value:function(o){this.childNodes.push(o)}},{key:"cloneNode",value:function(){var o=Object.create(this);return Object.assign(o,this),o}},{key:"removeChild",value:function(o){var h=this.childNodes.findIndex(function(v){return v===o});return h>-1?this.childNodes.splice(h,1):null}}]),a}(ae);function te(n){var l=St();return function(){var s=(0,b.Z)(n),o;if(l){var h=(0,b.Z)(this).constructor;o=Reflect.construct(s,arguments,h)}else o=s.apply(this,arguments);return(0,P.Z)(this,o)}}function St(){if(typeof Reflect=="undefined"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(n){return!1}}var qt=function(n){(0,Zt.Z)(a,n);var l=te(a);function a(){var s;return(0,ct.Z)(this,a),s=l.call(this),s.className="",s.children=[],s}return(0,et.Z)(a,[{key:"setAttribute",value:function(o,h){this[o]=h}},{key:"getAttribute",value:function(o){return this[o]}},{key:"setAttributeNS",value:function(o,h){this[o]=h}},{key:"getAttributeNS",value:function(o){return this[o]}}]),a}(ie),$t=typeof my!="undefined"&&!!my&&typeof my.showToast=="function"&&my.isFRM!==!0,Ct,Tt,ue,Fe;if($t){var We=my.getSystemInfoSync();Ct=We.screenWidth,Tt=We.screenHeight,ue=We.windowWidth,Fe=We.windowHeight}else{var zt=dt.innerWidth,At=dt.innerHeight;Ct=zt,Tt=At,ue=zt,Fe=At}var Mt={width:Ct,height:Tt,availWidth:ue,availHeight:Fe,availLeft:0,availTop:0},Rt=Mt.availWidth,tt=Mt.availHeight,_t={style:[]};function Xt(n,l){if(!("parentNode"in n)){var a;l===0?a=function(){return null}:l===1?a=function(){return _t}:a=function(){return null},Object.defineProperty(n,"parentNode",{enumerable:!0,get:a})}if(!("parentElement"in n)){var s;l===0?s=function(){return null}:l===1?s=function(){return _t}:s=function(){return null},Object.defineProperty(n,"parentElement",{enumerable:!0,get:s})}}function ve(n){n.style=n.style||{},Object.assign(n.style,{top:"0px",left:"0px",width:Rt+"px",height:tt+"px",margin:"0px",padding:"0px"})}function Wt(n){"clientLeft"in n||(n.clientLeft=0,n.clientTop=0),"clientWidth"in n||(n.clientWidth=Rt,n.clientHeight=tt),"getBoundingClientRect"in n||(n.getBoundingClientRect=function(){var l={x:0,y:0,top:0,left:0,width:this.clientWidth,height:this.clientHeight,right:this.clientWidth,bottom:this.clientHeight};return l})}function Me(n){"offsetLeft"in n||(n.offsetLeft=0,n.offsetTop=0),"offsetWidth"in n||(n.offsetWidth=Rt,n.offsetHeight=tt)}function je(n){"scrollLeft"in n||(n.scrollLeft=0,n.scrollTop=0),"scrollWidth"in n||(n.scrollWidth=Rt,n.scrollHeight=tt)}function Qe(n){var l=function(){return!0};n.classList=[],n.classList.add=l,n.classList.remove=l,n.classList.contains=l,n.classList.toggle=l}function pr(n){var l=tr();return function(){var s=(0,b.Z)(n),o;if(l){var h=(0,b.Z)(this).constructor;o=Reflect.construct(s,arguments,h)}else o=s.apply(this,arguments);return(0,P.Z)(this,o)}}function tr(){if(typeof Reflect=="undefined"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(n){return!1}}function Dr(){}var kr=function(n){(0,Zt.Z)(a,n);var l=pr(a);function a(){var s,o=arguments.length>0&&arguments[0]!==void 0?arguments[0]:"",h=arguments.length>1?arguments[1]:void 0;return(0,ct.Z)(this,a),s=l.call(this),s.className="",s.children=[],s.focus=Dr,s.blur=Dr,s.insertBefore=Dr,s.appendChild=Dr,s.removeChild=Dr,s.remove=Dr,s.innerHTML="",s.tagName=o.toUpperCase(),Xt((0,st.Z)(s),h),ve((0,st.Z)(s)),Qe((0,st.Z)(s)),Wt((0,st.Z)(s)),Me((0,st.Z)(s)),je((0,st.Z)(s)),s}return(0,et.Z)(a)}(qt);function Zr(n){var l=Tn();return function(){var s=(0,b.Z)(n),o;if(l){var h=(0,b.Z)(this).constructor;o=Reflect.construct(s,arguments,h)}else o=s.apply(this,arguments);return(0,P.Z)(this,o)}}function Tn(){if(typeof Reflect=="undefined"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(n){return!1}}var rn=function(n){(0,Zt.Z)(a,n);var l=Zr(a);function a(s){return(0,ct.Z)(this,a),l.call(this,s)}return(0,et.Z)(a,[{key:"addTextTrack",value:function(){}},{key:"captureStream",value:function(){}},{key:"fastSeek",value:function(){}},{key:"load",value:function(){}},{key:"pause",value:function(){}},{key:"play",value:function(){}}]),a}(kr);function Nn(n){var l=Un();return function(){var s=(0,b.Z)(n),o;if(l){var h=(0,b.Z)(this).constructor;o=Reflect.construct(s,arguments,h)}else o=s.apply(this,arguments);return(0,P.Z)(this,o)}}function Un(){if(typeof Reflect=="undefined"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(n){return!1}}var jn=function(n){(0,Zt.Z)(a,n);var l=Nn(a);function a(){return(0,ct.Z)(this,a),l.call(this,"video")}return(0,et.Z)(a)}(rn),wn={},Wn={};function Bt(n,l){wn=n,wn.id=l,"tagName"in wn||(wn.tagName="CANVAS"),wn.type="canvas",Mixin.parentNode(wn),Mixin.style(wn),Mixin.classList(wn),Mixin.clientRegion(wn),Mixin.offsetRegion(wn),wn.focus=function(){},wn.blur=function(){}}function Yt(n,l){var a=1024,s=1024;Wn={width:a,height:s,clientWidth:a/devicePixelRatio,clientHeight:s/devicePixelRatio,id:l,type:"canvas"},"tagName"in Wn||(Wn.tagName="CANVAS"),Mixin.parentNode(Wn),Mixin.style(Wn),Mixin.classList(Wn),Mixin.clientRegion(Wn),Mixin.offsetRegion(Wn),Wn.getContext=function(o){if(o==="2d")return n},Wn.focus=function(){},Wn.blur=function(){}}function oe(){return wn}function Ce(){return Wn}var Ze=(0,et.Z)(function n(){(0,ct.Z)(this,n);var l=oe(),a=l.createImage&&l.createImage()||{};return"tagName"in a||(a.tagName="IMG",a.__proto__=n.prototype),Xt(a),Qe(a),Object.assign(a,{addEventListener:function(o,h){a["on".concat(o)]=h.bind(a)},removeEventListener:function(o){a["on".concat(o)]=null}}),a}),hr={href:"",protocol:"",host:""};function Cr(n){var l=Pe();return function(){var s=(0,b.Z)(n),o;if(l){var h=(0,b.Z)(this).constructor;o=Reflect.construct(s,arguments,h)}else o=s.apply(this,arguments);return(0,P.Z)(this,o)}}function Pe(){if(typeof Reflect=="undefined"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(n){return!1}}var ur=function(n){(0,Zt.Z)(a,n);var l=Cr(a);function a(){return(0,ct.Z)(this,a),l.call(this,"body",0)}return(0,et.Z)(a,[{key:"addEventListener",value:function(o,h){var v=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{}}},{key:"removeEventListener",value:function(o,h,v){}},{key:"dispatchEvent",value:function(o){}}]),a}(kr),Hr=function(n){(0,Zt.Z)(a,n);var l=Cr(a);function a(){return(0,ct.Z)(this,a),l.call(this,"html",0)}return(0,et.Z)(a,[{key:"addEventListener",value:function(o,h){var v=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{}}},{key:"removeEventListener",value:function(o,h){}},{key:"dispatchEvent",value:function(o){}}]),a}(kr),Sn={},$r={readyState:"complete",visibilityState:"visible",hidden:!1,fullscreen:!0,scripts:[],style:{},location:hr,ontouchstart:null,ontouchmove:null,ontouchend:null,onvisibilitychange:null,parentNode:null,parentElement:null,head:null,body:null,documentElement:{style:[]},createElement:function(l){return l=l.toLowerCase(),l==="canvas"?my.createOffscreenCanvas(1024,128,"2d"):l==="img"?new Ze:l==="video"?new jn:new kr(l)},createElementNS:function(l,a){return this.createElement(a)},createTextNode:function(l){return l},getElementById:function(l){return null},getElementsByTagName:function(l){return l=l.toLowerCase(),[]},getElementsByTagNameNS:function(l,a){return this.getElementsByTagName(a)},getElementsByName:function(l){return[]},querySelector:function(l){return null},querySelectorAll:function(l){return[]},addEventListener:function(l,a,s){Sn[l]||(Sn[l]=[]),Sn[l].push(a)},removeEventListener:function(l,a){var s=Sn[l];if(s&&s.length>0){for(var o=s.length;o--;o>0)if(s[o]===a){s.splice(o,1);break}}},dispatchEvent:function(l){var a=l.type,s=Sn[a];if(s)for(var o=0;o-1,presto:n.indexOf("Presto")>-1,webKit:n.indexOf("AppleWebKit")>-1,gecko:n.indexOf("Gecko")>-1&&n.indexOf("KHTML")===-1,mobile:!!n.match(/AppleWebKit.*Mobile.*/),ios:!!n.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/),android:n.indexOf("Android")>-1||n.indexOf("Linux")>-1,iPhone:n.indexOf("iPhone")>-1,iPad:n.indexOf("iPad")>-1,webApp:n.indexOf("Safari")===-1,weixin:n.indexOf("MicroMessenger")>-1,qq:n.match(/\sQQ/i)===" qq"}}(),language:(dt.navigator.browserLanguage||dt.navigator.language).toLowerCase()};na.versions.android?ai="android":na.versions.trident?ai="IE":na.versions.presto?ai="Opera":na.versions.webKit?ai="webKit":na.versions.gecko?ai="Firefox":na.versions.mobile?ai="mobile":na.versions.ios?ai="ios":na.versions.iPhone?ai="iPhone":na.versions.iPad?ai="ipad":na.versions.webApp?ai="webApp":na.versions.weixin?ai="weixin":na.versions.qq&&(ai="qq"),qr=dt.navigator.userAgent,ui=na.language}var Ai=qr.toLowerCase().indexOf("android")!==-1,za=Ai?"Android; CPU Android 6.0":"iPhone; CPU iPhone OS 10_3_1 like Mac OS X",mo="Mozilla/5.0 (".concat(za,") AppleWebKit/603.1.30 (KHTML, like Gecko) Mobile/14E8301 MicroMessenger/6.6.0 MiniGame NetType/WIFI Language/").concat(ui),Za={platform:ai,language:ui,appVersion:"5.0 (".concat(za,") AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1"),userAgent:mo},$a={mark:function(l){return null},clearMeasures:function(l){return null},clearMarks:function(l){return null},measure:function(l,a,s){return{duration:0}},now:function(){}},Ma=0,_a=0;function oo(n){var l=Date.now(),a=Math.max(Ma+23,l);return _a=setTimeout(function(){n(Ma=a)},a-l),_a}function Zo(n){var l=oe();return l.requestAnimationFrame?l.requestAnimationFrame(n):oo(n)}function yo(n){var l=oe();return l.cancelAnimationFrame?l.cancelAnimationFrame(n):clearTimeout(n)}var Xa=function(){function n(l){var a=arguments.length>1&&arguments[1]!==void 0?arguments[1]:"";if((0,ct.Z)(this,n),l.indexOf("http://")==0||l.indexOf("https://")==0){this.href=l;return}this.href=a+l}return(0,et.Z)(n,null,[{key:"createObjectURL",value:function(a){var s=a.buffers[0],o=a.type,h=ro(s),v="data:".concat(o,";base64, ");return v+h}}]),n}();function ro(n){for(var l="",a=new Uint8Array(n),s=a.byteLength,o=0;o1&&arguments[1]!==void 0?arguments[1]:{target:this};typeof this["on".concat(n)]=="function"&&this["on".concat(n)].call(this,l)}function In(n){var l=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{readyState:n};this.readyState=n,Qr.call(this,"readystatechange",l)}var Zn=function(n){(0,Zt.Z)(a,n);var l=jo(a);function a(){var s;return(0,ct.Z)(this,a),s=l.call(this),s.onabort=null,s.onerror=null,s.onload=null,s.onloadstart=null,s.onprogress=null,s.ontimeout=null,s.onloadend=null,s.onreadystatechange=null,s.readyState=0,s.response=null,s.responseText=null,s._responseType="text",s.responseXML=null,s.status=0,s.statusText="",s.upload={},s.withCredentials=!1,nr.set("requestHeader",{"content-type":"application/x-www-form-urlencoded"}),s}return(0,et.Z)(a,[{key:"responseType",set:function(o){this._responseType=o}},{key:"abort",value:function(){var o=He.get("requestTask");o&&o.abort()}},{key:"getAllResponseHeaders",value:function(){var o=xe.get("responseHeader");return Object.keys(o).map(function(h){return"".concat(h,": ").concat(o[h])}).join(` +`)}},{key:"getResponseHeader",value:function(o){return xe.get("responseHeader")[o]}},{key:"open",value:function(o,h,v){this._method=o,this._url=h}},{key:"overrideMimeType",value:function(){}},{key:"send",value:function(){var o=arguments.length>0&&arguments[0]!==void 0?arguments[0]:""}},{key:"setRequestHeader",value:function(o,h){var v=nr.get("requestHeader");v[o]=h,nr.set("requestHeader",v)}},{key:"addEventListener",value:function(o,h){var v=this;typeof h=="function"&&(this["on"+o]=function(){var m=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};m.target=m.target||v,h.call(v,m)})}},{key:"removeEventListener",value:function(o,h){this["on"+o]===h&&(this["on"+o]=null)}}]),a}(ae);Zn.UNSEND=0,Zn.OPENED=1,Zn.HEADERS_RECEIVED=2,Zn.LOADING=3,Zn.DONE=4;function Cn(n){var l=ta();return function(){var s=(0,b.Z)(n),o;if(l){var h=(0,b.Z)(this).constructor;o=Reflect.construct(s,arguments,h)}else o=s.apply(this,arguments);return(0,P.Z)(this,o)}}function ta(){if(typeof Reflect=="undefined"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(n){return!1}}var Ya=function(n){(0,Zt.Z)(a,n);var l=Cn(a);function a(s){var o;return(0,ct.Z)(this,a),o=l.call(this,s),o.touches=[],o.targetTouches=[],o.changedTouches=[],o.target=oe(),o.currentTarget=oe(),o}return(0,et.Z)(a)}(ge);function Qo(n){var l=n||{},a=l.x,s=a===void 0?0:a,o=l.y,h=o===void 0?0:o,v=l.clientX,m=v===void 0?0:v,E=l.clientY,A=E===void 0?0:E;Object.keys(n).indexOf("x")!==-1?(n.pageX=n.clientX=s,n.pageY=n.clientY=h):(n.x=m,n.y=A)}function Mo(n){return function(l){var a=new Ya(n);a.changedTouches=l.changedTouches,a.touches=l.touches,a.targetTouches=Array.prototype.slice.call(l.touches),a.timeStamp=l.timeStamp,a.changedTouches.forEach(function(s){return Qo(s)}),a.touches.forEach(function(s){return Qo(s)}),a.targetTouches.forEach(function(s){return Qo(s)}),$r.dispatchEvent(a)}}function rs(n){return function(l){l.type=n,$r.dispatchEvent(l)}}var qa=Mo("touchstart"),Vo=Mo("touchmove"),pu=Mo("touchend"),_s=rs("mapCameaParams");function Bl(n){var l=ff();return function(){var s=(0,b.Z)(n),o;if(l){var h=(0,b.Z)(this).constructor;o=Reflect.construct(s,arguments,h)}else o=s.apply(this,arguments);return(0,P.Z)(this,o)}}function ff(){if(typeof Reflect=="undefined"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(n){return!1}}var $u=function(n){(0,Zt.Z)(a,n);var l=Bl(a);function a(s){var o;return(0,ct.Z)(this,a),o=l.call(this,s),o.target=oe(),o.currentTarget=oe(),o}return(0,et.Z)(a)}(ge),ke=["bubbles","cancelable","view","detail","screenX","screenY","clientX","clientY","ctrlKey","altKey","shiftKey","metaKey","button","relatedTarget","pointerId","width","height","pressure","tiltX","tiltY","pointerType","hwTimestamp","isPrimary","pageX","pageY","timeStamp"],Xe=[!1,!1,null,null,0,0,0,0,!1,!1,!1,!1,0,null,0,0,0,0,0,0,0,"",0,!1,0,0,0],Tr="touch";function sn(n,l,a){for(var s=new $u(n),o=0;o2&&arguments[2]!==void 0?arguments[2]:{};$r.addEventListener(l,a,s)},removeEventListener:function(l,a,s){$r.removeEventListener(l,a)},dispatchEvent:function(l){$r.dispatchEvent(l)},innerWidth:Mt.availWidth,innerHeight:Mt.availHeight,setTimeout,clearTimeout,setInterval,clearInterval},Co=Su?Bc:dt,sc=Su?Zn:dt.XMLHttpRequest,Xl=Su?hr:dt.location;function il(n){var l=n;return typeof n=="string"&&(l=Co.document.getElementById(n)),l}function uc(n){return n.trim?n.trim():n.replace(/^\s+|\s+$/g,"")}function D(n){return uc(n).split(/\s+/)}function Y(n){var l,a,s=Co==null||(l=Co.document)===null||l===void 0||(a=l.documentElement)===null||a===void 0?void 0:a.style;if(!s)return n[0];for(var o in n)if(n[o]&&n[o]in s)return n[o];return n[0]}function bt(n,l,a){var s=Co.document.createElement(n);return l&&(s.className=l||""),a&&a.appendChild(s),s}function nt(n){var l=n.parentNode;l&&l.removeChild(n)}function se(n,l){if(n.classList!==void 0)for(var a=D(l),s=0,o=a.length;s0&&new RegExp("(^|\\s)"+l+"(\\s|$)").test(a)}function Gr(n,l){n instanceof HTMLElement?n.className=l:n.className.baseVal=l}function Vr(n){return n instanceof SVGElement&&(n=n.correspondingElement),n.className.baseVal===void 0?n.className:n.className.baseVal}function On(n){for(;n&&n.firstChild;)n.removeChild(n.firstChild)}var Gn=Y(["transform","WebkitTransform"]);function $i(n,l){n.style[Gn]=l}function Pa(){if(typeof Event=="function")Co.dispatchEvent(new Event("resize"));else{var n=Co.document.createEvent("UIEvents");n.initUIEvent("resize",!0,!1,Co,0),Co.dispatchEvent(n)}}function Ea(n){var l=["padding: "+(n.height/2-8)+"px "+n.width/2+"px;","line-height: "+n.height+"px;","background-image: url("+n.toDataURL()+");"];console.log(`%c +`,l.join(""))}function fo(){var n,l=Co.document.querySelector('meta[name="viewport"]');if(!l)return 1;var a=(n=l.content)===null||n===void 0?void 0:n.split(","),s=a.find(function(o){var h=o.split("="),v=(0,ht.Z)(h,1),m=v[0];return m==="initial-scale"});return s?s.split("=")[1]*1:1}var Aa=fo()<1?1:Co.devicePixelRatio;function Ao(n,l){n.setAttribute("style","".concat(n.style.cssText).concat(l))}function Ja(n){return n.split(";").map(function(l){return l.trim()}).filter(function(l){return l})}function vs(n,l){var a,s=Ja((a=n.getAttribute("style"))!==null&&a!==void 0?a:""),o=Ja(l),h=ee.pull.apply(void 0,[s].concat((0,me.Z)(o)));n.setAttribute("style",h.join(";"))}function Cu(n){return Object.entries(n).map(function(l){var a=(0,ht.Z)(l,2),s=a[0],o=a[1];return"".concat(s,": ").concat(o)}).join(";")}function al(n,l){return{left:n.left-l.left,top:n.top-l.top,right:l.left+l.width-n.left-n.width,bottom:l.top+l.height-n.top-n.height}}function Au(n,l){n.checked=l,l?n.setAttribute("checked","true"):n.removeAttribute("checked")}function nu(n){n.innerHTML=""}function Nc(n){n.setAttribute("draggable","false")}function Ks(n,l){if(typeof l=="string"){var a=document.createElement("div");for(a.innerHTML=l;a.firstChild;)n.append(a.firstChild)}else Array.isArray(l)?n.append.apply(n,(0,me.Z)(l)):n.append(l)}function Yl(n){if(n.length===0)throw new Error("max requires at least one data point");for(var l=n[0],a=1;al&&(l=n[a]);return l*1}function ye(n){if(n.length===0)throw new Error("min requires at least one data point");for(var l=n[0],a=1;as&&(s=o,a=l),o=1,l=n[h]):o++;return a*1}var xn={min:ye,max:Yl,mean:Or,sum:ir,mode:Xr};function yi(n,l){return n.map(function(a){return a[l]})}function qi(n,l){return xn[n](l)}var ka=ot(89611);function du(n){return Function.toString.call(n).indexOf("[native code]")!==-1}var Qs=ot(78814);function Ka(n,l,a){return(0,Qs.Z)()?Ka=Reflect.construct.bind():Ka=function(o,h,v){var m=[null];m.push.apply(m,h);var E=Function.bind.apply(o,m),A=new E;return v&&(0,ka.Z)(A,v.prototype),A},Ka.apply(null,arguments)}function Nl(n){var l=typeof Map=="function"?new Map:void 0;return Nl=function(s){if(s===null||!du(s))return s;if(typeof s!="function")throw new TypeError("Super expression must either be null or a function");if(typeof l!="undefined"){if(l.has(s))return l.get(s);l.set(s,o)}function o(){return Ka(s,arguments,(0,b.Z)(this).constructor)}return o.prototype=Object.create(s.prototype,{constructor:{value:o,enumerable:!1,writable:!0,configurable:!0}}),(0,ka.Z)(o,s)},Nl(n)}function tv(n){var l=hf();return function(){var s=(0,b.Z)(n),o;if(l){var h=(0,b.Z)(this).constructor;o=Reflect.construct(s,arguments,h)}else o=s.apply(this,arguments);return(0,P.Z)(this,o)}}function hf(){if(typeof Reflect=="undefined"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(n){return!1}}var vd=function(n){(0,Zt.Z)(a,n);var l=tv(a);function a(s,o,h,v){var m;return(0,ct.Z)(this,a),m=l.call(this,"AJAXError: ".concat(o," (").concat(s,"): ").concat(h)),m.status=s,m.statusText=o,m.url=h,m.body=v,m}return(0,et.Z)(a)}(Nl(Error));function ev(n,l){var a=new sc,s=Array.isArray(n.url)?n.url[0]:n.url;a.open(n.method||"GET",s,!0),n.type==="arrayBuffer"&&(a.responseType="arraybuffer");for(var o in n.headers)n.headers.hasOwnProperty(o)&&a.setRequestHeader(o,n.headers[o]);return n.type==="json"&&(a.responseType="text",a.setRequestHeader("Accept","application/json")),a.withCredentials=n.credentials==="include",a.onerror=function(){l(new Error(a.statusText))},a.onload=function(){if((a.status>=200&&a.status<300||a.status===0)&&a.response!==null){var h=a.response;if(n.type==="json")try{h=JSON.parse(a.response)}catch(m){return l(m)}l(null,h,a.getResponseHeader("Cache-Control"),a.getResponseHeader("Expires"),a)}else{var v=new Blob([a.response],{type:a.getResponseHeader("Content-Type")});l(new vd(a.status,a.statusText,s.toString(),v))}},a.send(n.body),a}function rv(n){return new Promise(function(l,a){ev(n,function(s,o,h,v,m){s?a({err:s,data:null,xhr:m}):l({err:null,data:o,cacheControl:h,expires:v,xhr:m})})})}function uh(n,l){return ev(n,l)}var Df=function(l,a){return uh((0,q.Z)((0,q.Z)({},l),{},{type:"json"}),a)},zc=function(l,a){return uh((0,q.Z)((0,q.Z)({},l),{},{type:"arrayBuffer"}),a)},lh=function(l,a){return uh(_objectSpread(_objectSpread({},l),{},{method:"POST"}),a)};function Bf(n){var l=$window.document.createElement("a");return l.href=n,l.protocol===$window.document.location.protocol&&l.host===$window.document.location.host}var lc="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYV2NgAAIAAAUAAarVyFEAAAAASUVORK5CYII=";function ol(n,l){var a=new Co.Image,s=Co.URL||Co.webkitURL;a.crossOrigin="anonymous",a.onload=function(){l(null,a),s.revokeObjectURL(a.src),a.onload=null,window.requestAnimationFrame(function(){a.src=lc})},a.onerror=function(){return l(new Error("Could not load image. Please make sure to use a supported image type such as PNG or JPEG. Note that SVGs are not supported."))};var o=new Blob([new Uint8Array(n)],{type:"image/png"});a.src=n.byteLength?s.createObjectURL(o):lc}function pf(n,l){var a=new Blob([new Uint8Array(n)],{type:"image/png"});createImageBitmap(a).then(function(s){l(null,s)}).catch(function(s){l(new Error("Could not load image because of ".concat(s.message,". Please make sure to use a supported image type such as PNG or JPEG. Note that SVGs are not supported.")))})}var gl=function(l,a,s){var o=function(v,m){if(v)a(v);else if(m){var E=typeof createImageBitmap=="function",A=s?s(m):m;E?pf(A,a):ol(A,a)}};return l.type==="json"?Df(l,o):zc(l,o)},Tu=63710088e-1,Gh={centimeters:Tu*100,centimetres:Tu*100,degrees:Tu/111325,feet:Tu*3.28084,inches:Tu*39.37,kilometers:Tu/1e3,kilometres:Tu/1e3,meters:Tu,metres:Tu,miles:Tu/1609.344,millimeters:Tu*1e3,millimetres:Tu*1e3,nauticalmiles:Tu/1852,radians:1,yards:Tu*1.0936},Mp={centimeters:100,centimetres:100,degrees:1/111325,feet:3.28084,inches:39.37,kilometers:1/1e3,kilometres:1/1e3,meters:1,metres:1,miles:1/1609.344,millimeters:1e3,millimetres:1e3,nauticalmiles:1/1852,radians:1/Tu,yards:1.0936133},wc={acres:247105e-9,centimeters:1e4,centimetres:1e4,feet:10.763910417,hectares:1e-4,inches:1550.003100006,kilometers:1e-6,kilometres:1e-6,meters:1,metres:1,miles:386e-9,millimeters:1e6,millimetres:1e6,yards:1.195990046};function ml(n,l,a){a===void 0&&(a={});var s={type:"Feature"};return(a.id===0||a.id)&&(s.id=a.id),a.bbox&&(s.bbox=a.bbox),s.properties=l||{},s.geometry=n,s}function Hh(n,l,a){switch(a===void 0&&(a={}),n){case"Point":return jh(l).geometry;case"LineString":return $l(l).geometry;case"Polygon":return Uc(l).geometry;case"MultiPoint":return gd(l).geometry;case"MultiLineString":return Il(l).geometry;case"MultiPolygon":return md(l).geometry;default:throw new Error(n+" is invalid")}}function jh(n,l,a){if(a===void 0&&(a={}),!n)throw new Error("coordinates is required");if(!Array.isArray(n))throw new Error("coordinates must be an Array");if(n.length<2)throw new Error("coordinates must be at least 2 numbers long");if(!Ku(n[0])||!Ku(n[1]))throw new Error("coordinates must contain numbers");var s={type:"Point",coordinates:n};return ml(s,l,a)}function Ip(n,l,a){return a===void 0&&(a={}),Nf(n.map(function(s){return jh(s,l)}),a)}function Uc(n,l,a){a===void 0&&(a={});for(var s=0,o=n;s=0))throw new Error("precision must be a positive number");var a=Math.pow(10,l||0);return Math.round(n*a)/a}function _d(n,l){l===void 0&&(l="kilometers");var a=Gh[l];if(!a)throw new Error(l+" units is invalid");return n*a}function df(n,l){l===void 0&&(l="kilometers");var a=Gh[l];if(!a)throw new Error(l+" units is invalid");return n/a}function bs(n,l){return zf(df(n,l))}function Yh(n){var l=n%360;return l<0&&(l+=360),l}function zf(n){var l=n%(2*Math.PI);return l*180/Math.PI}function zl(n){var l=n%360;return l*Math.PI/180}function _o(n,l,a){if(l===void 0&&(l="kilometers"),a===void 0&&(a="kilometers"),!(n>=0))throw new Error("length must be a positive number");return _d(df(n,l),a)}function ns(n,l,a){if(l===void 0&&(l="meters"),a===void 0&&(a="kilometers"),!(n>=0))throw new Error("area must be a positive number");var s=wc[l];if(!s)throw new Error("invalid original units");var o=wc[a];if(!o)throw new Error("invalid final units");return n/s*o}function Ku(n){return!isNaN(n)&&n!==null&&!Array.isArray(n)}function kp(n){return!!n&&n.constructor===Object}function vf(n){if(!n)throw new Error("bbox is required");if(!Array.isArray(n))throw new Error("bbox must be an Array");if(n.length!==4&&n.length!==6)throw new Error("bbox must be an Array of 4 or 6 numbers");n.forEach(function(l){if(!Ku(l))throw new Error("bbox must only contain numbers")})}function $h(n){if(!n)throw new Error("id is required");if(["string","number"].indexOf(typeof n)===-1)throw new Error("id must be a number or a string")}function xd(n){return typeof n=="number"}function qh(n,l,a){if(n!==null)for(var s,o,h,v,m,E,A,F=0,B=0,V,j=n.type,at=j==="FeatureCollection",ut=j==="Feature",xt=at?n.features.length:1,Nt=0;NtE||at>A||ut>F){m=B,E=s,A=at,F=ut,h=0;return}var xt=lineString([m,B],a.properties);if(l(xt,s,o,ut,h)===!1)return!1;h++,m=B})===!1)return!1}}})}function cc(n,l,a){var s=a,o=!1;return Uf(n,function(h,v,m,E,A){o===!1&&a===void 0?s=h:s=l(s,h,v,m,E,A),o=!0}),s}function yl(n,l){if(!n)throw new Error("geojson is required");is(n,function(a,s,o){if(a.geometry!==null){var h=a.geometry.type,v=a.geometry.coordinates;switch(h){case"LineString":if(l(a,s,o,0,0)===!1)return!1;break;case"Polygon":for(var m=0;ma[0]&&(l[0]=a[0]),l[1]>a[1]&&(l[1]=a[1]),l[2]s&&n.lng<=h&&n.lat>o&&n.lat<=v}function Vf(n){var l=[1/0,1/0,-1/0,-1/0];return n.forEach(function(a){var s=a.coordinates;fc(l,s)}),l}function fc(n,l){return Array.isArray(l[0])?l.forEach(function(a){fc(n,a)}):(n[0]>l[0]&&(n[0]=l[0]),n[1]>l[1]&&(n[1]=l[1]),n[2]1&&arguments[1]!==void 0?arguments[1]:!0,a=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{enable:!0,decimal:1};n=kl(n,l);var s=n[0],o=n[1],h=s*xa/180,v=Math.log(Math.tan((90+o)*Math.PI/360))/(Math.PI/180);return v=v*xa/180,a.enable&&(h=Number(h.toFixed(a.decimal)),v=Number(v.toFixed(a.decimal))),n.length===3?[h,v,n[2]]:[h,v]}function Fp(n){var l=arguments.length>1&&arguments[1]!==void 0?arguments[1]:6,a=n[0],s=n[1],o=a/xa*180,h=s/xa*180;return h=180/Math.PI*(2*Math.atan(Math.exp(h*Math.PI/180))-Math.PI/2),l!=null&&(o=Number(o.toFixed(l)),h=Number(h.toFixed(l))),n.length===3?[o,h,n[2]]:[o,h]}function bd(n){if(n==null)throw new Error("lng is required");return(n>180||n<-180)&&(n=n%360,n>180&&(n=-360+n),n<-180&&(n=360+n),n===0&&(n=0)),n}function Ed(n){if(n==null)throw new Error("lat is required");return(n>90||n<-90)&&(n=n%180,n>90&&(n=-180+n),n<-90&&(n=180+n),n===0&&(n=0)),n}function kl(n,l){if(l===!1)return n;var a=bd(n[0]),s=Ed(n[1]);return s>85&&(s=85),s<-85&&(s=-85),n.length===3?[a,s,n[2]]:[a,s]}function qo(n){var l=85.0511287798,a=Math.max(Math.min(l,n[1]),-l),s=256<<20,o=Math.PI/180,h=n[0]*o,v=a*o;v=Math.log(Math.tan(Math.PI/4+v/2));var m=.5/Math.PI,E=.5,A=-.5/Math.PI;return o=.5,h=s*(m*h+E),v=s*(A*v+o),[Math.floor(h),Math.floor(v)]}function to(n){var l=.5/Math.PI,a=.5,s=-.5/Math.PI,o=.5,h=256<<20,v=_slicedToArray(n,2),m=v[0],E=v[1];m=(m/h-a)/l,E=(E/h-o)/s,E=(Math.atan(Math.pow(Math.E,E))-Math.PI/4)*2,o=Math.PI/180;var A=E/o,F=m/o;return[F,A]}function bl(n,l){var a=85.0511287798,s=Math.PI/180,o=6378137;return l=Math.max(Math.min(a,l),-a),n*=s,l*=s,l=Math.log(Math.tan(Math.PI/4+l/2)),[n*o,l*o]}function ql(n,l){var a=Math.PI/180,s=6378137,o=n/s/a,h=2*(Math.atan(Math.exp(l/s))-Math.PI/4)/a;return[o,h]}function Ps(n,l,a){var s=zl(l[1]-n[1]),o=zl(l[0]-n[0]),h=zl(n[1]),v=zl(l[1]),m=Math.pow(Math.sin(s/2),2)+Math.pow(Math.sin(o/2),2)*Math.cos(h)*Math.cos(v);return _d(2*Math.atan2(Math.sqrt(m),Math.sqrt(1-m)),a="meters")}function Zc(n){var l=Math.PI/180,a=85.0511287798,s=6378137,o=Math.max(Math.min(a,n[1]),-a),h=Math.sin(o*l),v=s*n[0]*l,m=s*Math.log((1+h)/(1-h))/2;return[v,m]}function Ul(n,l){var a=Math.abs(n[1][1]-n[0][1])*l,s=Math.abs(n[1][0]-n[0][0])*l;return[[n[0][0]-s,n[0][1]-a],[n[1][0]+s,n[1][1]+a]]}function Mu(n,l){return n[0][0]<=l[0][0]&&n[0][1]<=l[0][1]&&n[1][0]>=l[1][0]&&n[1][1]>=l[1][1]}function Iu(n){return[[n[0],n[1]],[n[2],n[3]]]}function Ll(n){var l=Ki(n,[0,0]);return[n[0]/l,n[1]/l]}function Ki(n,l){return Math.sqrt(Math.pow(n[0]-l[0],2)+Math.pow(n[1]-l[1],2))}function ji(n,l){return n[0]*l[0]+n[1]*l[1]}function xf(n){return Math.sqrt(n[0]*n[0]+n[1]*n[1])}function sl(n,l){return Math.acos(ji(n,l)/(xf(n)*xf(l)))*180/Math.PI}function ul(n,l){return l[0]>0?l[1]>0?90-Math.atan(l[1]/l[0])*180/Math.PI:90+Math.atan(-l[1]/l[0])*180/Math.PI:l[1]<0?180+(90-Math.atan(l[1]/l[0])*180/Math.PI):270+Math.atan(l[1]/-l[0])*180/Math.PI}function Bu(n){var l=arguments.length>1&&arguments[1]!==void 0?arguments[1]:100;if(!(!n||n.length<2)){for(var a=[0,1],s=0,o=[],h=0;h0){var B=o[h-1].rotation;B-F>360-B+F&&(F=F+360)}o.push({start:v,end:m,dis:E,rotation:F,duration:0})}return o.map(function(V){V.duration=l*(V.dis/s)}),o}}function Es(n){if(xd(n[0]))return n;if(xd(n[0][0]))throw new Error("\u5F53\u524D\u6570\u636E\u4E0D\u652F\u6301\u6807\u6CE8");if(xd(n[0][0][0])){var l=n,a=0,s=0,o=0;return l.forEach(function(h){h.forEach(function(v){a+=v[0],s+=v[1],o++})}),[a/o,s/o,0]}else throw new Error("\u5F53\u524D\u6570\u636E\u4E0D\u652F\u6301\u6807\u6CE8")}function Cc(n){for(var l=n[0],a=n[1],s=n[0],o=n[1],h=0,v=0,m=0,E=0;E0&&arguments[0]!==void 0?arguments[0]:50,a=arguments.length>1?arguments[1]:void 0;(0,ct.Z)(this,n),this.limit=l,this.destroy=a||this.defaultDestroy,this.order=[],this.clear()}return(0,et.Z)(n,[{key:"clear",value:function(){var a=this;this.order.forEach(function(s){a.delete(s)}),this.cache={},this.order=[]}},{key:"get",value:function(a){var s=this.cache[a];return s&&(this.deleteOrder(a),this.appendOrder(a)),s}},{key:"set",value:function(a,s){this.cache[a]?(this.delete(a),this.cache[a]=s,this.appendOrder(a)):(Object.keys(this.cache).length===this.limit&&this.delete(this.order[0]),this.cache[a]=s,this.appendOrder(a))}},{key:"delete",value:function(a){var s=this.cache[a];s&&(this.deleteCache(a),this.deleteOrder(a),this.destroy(s,a))}},{key:"deleteCache",value:function(a){delete this.cache[a]}},{key:"deleteOrder",value:function(a){var s=this.order.findIndex(function(o){return o===a});s>=0&&this.order.splice(s,1)}},{key:"appendOrder",value:function(a){this.order.push(a)}},{key:"defaultDestroy",value:function(a,s){return null}}]),n}();function as(n,l){n.forEach(function(a){!l[a]||(l[a]=l[a].bind(l))})}var Jh=null;function Vc(n,l,a){n.prototype=l.prototype=a,a.constructor=n}function hc(n,l){var a=Object.create(n.prototype);for(var s in l)a[s]=l[s];return a}function ll(){}var Ns=.7,Os=1/Ns,Qu="\\s*([+-]?\\d+)\\s*",Gc="\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\s*",cl="\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)%\\s*",Gf=/^#([0-9a-f]{3,8})$/,Tc=new RegExp("^rgb\\("+[Qu,Qu,Qu]+"\\)$"),fh=new RegExp("^rgb\\("+[cl,cl,cl]+"\\)$"),Hf=new RegExp("^rgba\\("+[Qu,Qu,Qu,Gc]+"\\)$"),wd=new RegExp("^rgba\\("+[cl,cl,cl,Gc]+"\\)$"),zs=new RegExp("^hsl\\("+[Gc,cl,cl]+"\\)$"),Mc=new RegExp("^hsla\\("+[Gc,cl,cl,Gc]+"\\)$"),tp={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074};Vc(ll,pc,{copy:function(n){return Object.assign(new this.constructor,this,n)},displayable:function(){return this.rgb().displayable()},hex:El,formatHex:El,formatHsl:Zl,formatRgb:ep,toString:ep});function El(){return this.rgb().formatHex()}function Zl(){return ft(this).formatHsl()}function ep(){return this.rgb().formatRgb()}function pc(n){var l,a;return n=(n+"").trim().toLowerCase(),(l=Gf.exec(n))?(a=l[1].length,l=parseInt(l[1],16),a===6?Rp(l):a===3?new w(l>>8&15|l>>4&240,l>>4&15|l&240,(l&15)<<4|l&15,1):a===8?Hc(l>>24&255,l>>16&255,l>>8&255,(l&255)/255):a===4?Hc(l>>12&15|l>>8&240,l>>8&15|l>>4&240,l>>4&15|l&240,((l&15)<<4|l&15)/255):null):(l=Tc.exec(n))?new w(l[1],l[2],l[3],1):(l=fh.exec(n))?new w(l[1]*255/100,l[2]*255/100,l[3]*255/100,1):(l=Hf.exec(n))?Hc(l[1],l[2],l[3],l[4]):(l=wd.exec(n))?Hc(l[1]*255/100,l[2]*255/100,l[3]*255/100,l[4]):(l=zs.exec(n))?$(l[1],l[2]/100,l[3]/100,1):(l=Mc.exec(n))?$(l[1],l[2]/100,l[3]/100,l[4]):tp.hasOwnProperty(n)?Rp(tp[n]):n==="transparent"?new w(NaN,NaN,NaN,0):null}function Rp(n){return new w(n>>16&255,n>>8&255,n&255,1)}function Hc(n,l,a,s){return s<=0&&(n=l=a=NaN),new w(n,l,a,s)}function x(n){return n instanceof ll||(n=pc(n)),n?(n=n.rgb(),new w(n.r,n.g,n.b,n.opacity)):new w}function C(n,l,a,s){return arguments.length===1?x(n):new w(n,l,a,s==null?1:s)}function w(n,l,a,s){this.r=+n,this.g=+l,this.b=+a,this.opacity=+s}Vc(w,C,hc(ll,{brighter:function(n){return n=n==null?Os:Math.pow(Os,n),new w(this.r*n,this.g*n,this.b*n,this.opacity)},darker:function(n){return n=n==null?Ns:Math.pow(Ns,n),new w(this.r*n,this.g*n,this.b*n,this.opacity)},rgb:function(){return this},displayable:function(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:M,formatHex:M,formatRgb:z,toString:z}));function M(){return"#"+H(this.r)+H(this.g)+H(this.b)}function z(){var n=this.opacity;return n=isNaN(n)?1:Math.max(0,Math.min(1,n)),(n===1?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(n===1?")":", "+n+")")}function H(n){return n=Math.max(0,Math.min(255,Math.round(n)||0)),(n<16?"0":"")+n.toString(16)}function $(n,l,a,s){return s<=0?n=l=a=NaN:a<=0||a>=1?n=l=NaN:l<=0&&(n=NaN),new re(n,l,a,s)}function ft(n){if(n instanceof re)return new re(n.h,n.s,n.l,n.opacity);if(n instanceof ll||(n=pc(n)),!n)return new re;if(n instanceof re)return n;n=n.rgb();var l=n.r/255,a=n.g/255,s=n.b/255,o=Math.min(l,a,s),h=Math.max(l,a,s),v=NaN,m=h-o,E=(h+o)/2;return m?(l===h?v=(a-s)/m+(a0&&E<1?0:v,new re(v,m,E,n.opacity)}function Ut(n,l,a,s){return arguments.length===1?ft(n):new re(n,l,a,s==null?1:s)}function re(n,l,a,s){this.h=+n,this.s=+l,this.l=+a,this.opacity=+s}Vc(re,Ut,hc(ll,{brighter:function(n){return n=n==null?Os:Math.pow(Os,n),new re(this.h,this.s,this.l*n,this.opacity)},darker:function(n){return n=n==null?Ns:Math.pow(Ns,n),new re(this.h,this.s,this.l*n,this.opacity)},rgb:function(){var n=this.h%360+(this.h<0)*360,l=isNaN(n)||isNaN(this.s)?0:this.s,a=this.l,s=a+(a<.5?a:1-a)*l,o=2*a-s;return new w(De(n>=240?n-240:n+120,o,s),De(n,o,s),De(n<120?n+240:n-120,o,s),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl:function(){var n=this.opacity;return n=isNaN(n)?1:Math.max(0,Math.min(1,n)),(n===1?"hsl(":"hsla(")+(this.h||0)+", "+(this.s||0)*100+"%, "+(this.l||0)*100+"%"+(n===1?")":", "+n+")")}}));function De(n,l,a){return(n<60?l+(a-l)*n/60:n<180?a:n<240?l+(a-l)*(240-n)/60:l)*255}function Be(n){return typeof n=="string"?!!d3.color(n):!1}function Le(n){var l=pc(n),a=[0,0,0,0];return l!=null&&(a[0]=l.r/255,a[1]=l.g/255,a[2]=l.b/255,a[3]=l.opacity),a}function cr(n){var l=n&&n[0],a=n&&n[1],s=n&&n[2],o=l+a*256+s*65536-1;return o}function ar(n){return[n+1&255,n+1>>8&255,n+1>>8>>8&255]}function Er(n){var l=Co.document.createElement("canvas"),a=l.getContext("2d");l.width=256,l.height=1;var s=null;if(n.weights){var o=0;n.weights.map(function(V,j){var at=n.colors[j]||"rgba(0, 0, 0, 0)",ut=o+V;a.fillStyle=at,a.fillRect(o*256,0,ut*256,1),o=ut})}else{for(var h=a.createLinearGradient(0,0,256,1),v=n.positions[0],m=n.positions[n.positions.length-1],E=0;E0&&h[h.length-1])&&(A[0]===6||A[0]===2)){a=0;continue}if(A[0]===3&&(!h||A[1]>h[0]&&A[1]this.getMaxConcurrency();a?(l.destroy(),this.count--):this.idleQueue.push(l),this.isDestroyed||this.startQueuedJob()},n.prototype.getAvailableWorker=function(){if(this.idleQueue.length>0)return this.idleQueue.shift()||null;if(this.count=n.length&&(n=void 0),{value:n&&n[s++],done:!n}}};throw new TypeError(l?"Object is not iterable.":"Symbol.iterator is not defined.")},Ic={maxConcurrency:3,maxMobileConcurrency:1,onDebug:function(){},reuseWorkers:!0},Ju=function(){function n(l){this.workerPools=new Map,this.props=vu({},Ic),this.setProps(l),this.workerPools=new Map}return n.isSupported=function(){return Us.isSupported()},n.getWorkerFarm=function(l){return l===void 0&&(l={}),n.workerFarm=n.workerFarm||new n({}),n.workerFarm.setProps(l),n.workerFarm},n.prototype.destroy=function(){var l,a;try{for(var s=Nu(this.workerPools.values()),o=s.next();!o.done;o=s.next()){var h=o.value;h.destroy()}}catch(v){l={error:v}}finally{try{o&&!o.done&&(a=s.return)&&a.call(s)}finally{if(l)throw l.error}}},n.prototype.setProps=function(l){var a,s;this.props=vu(vu({},this.props),l);try{for(var o=Nu(this.workerPools.values()),h=o.next();!h.done;h=o.next()){var v=h.value;v.setProps(this.getWorkerPoolProps())}}catch(m){a={error:m}}finally{try{h&&!h.done&&(s=o.return)&&s.call(o)}finally{if(a)throw a.error}}},n.prototype.getWorkerPool=function(l){var a=l.name,s=l.source,o=l.url,h=this.workerPools.get(a);return h||(h=new ls({name:a,source:s,url:o}),h.setProps(this.getWorkerPoolProps()),this.workerPools.set(a,h)),h},n.prototype.getWorkerPoolProps=function(){return{maxConcurrency:this.props.maxConcurrency,maxMobileConcurrency:this.props.maxMobileConcurrency,reuseWorkers:this.props.reuseWorkers,onDebug:this.props.onDebug}},n}(),Kl=Ju,fl=new Map,hh=function(){function n(){}return Object.defineProperty(n,"onmessage",{set:function(l){self.onmessage=function(a){if(!!Ql(a)){var s=a.data,o=s.type,h=s.payload;l(o,h)}}},enumerable:!1,configurable:!0}),n.addEventListener=function(l){var a=fl.get(l);a||(a=function(s){if(!!Ql(s)){var o=s.data,h=o.type,v=o.payload;l(h,v)}}),self.addEventListener("message",a)},n.removeEventListener=function(l){var a=fl.get(l);fl.delete(l),self.removeEventListener("message",a)},n.postMessage=function(l,a){if(self){var s={source:"Worker thread",type:l,payload:a},o=la(a);self.postMessage(s,o)}},n}(),wl=hh;function Ql(n){var l=n.type,a=n.data;return l==="message"&&a&&typeof a.source=="string"&&a.source==="Main thread"}function ph(n){return JSON.parse(jf(n))}function jf(n){var l=new Set;return JSON.stringify(n,function(a,s){if(typeof s=="object"&&s!==null){if(l.has(s))try{return JSON.parse(JSON.stringify(s))}catch(o){return}l.add(s)}return s})}function dc(n){return""+n.name}function dh(n,l){l===void 0&&(l={});var a=l[n.id]||{},s=n.name+".worker.js",o=a.workerUrl;return l._workerType==="test"&&(o=n.module+"/dist/"+s),o||(o="https://unpkg.com/"+n.module+"/dist/"+s),assert(o),o}var Jl=function(n,l,a,s){function o(h){return h instanceof a?h:new a(function(v){v(h)})}return new(a||(a=Promise))(function(h,v){function m(F){try{A(s.next(F))}catch(B){v(B)}}function E(F){try{A(s.throw(F))}catch(B){v(B)}}function A(F){F.done?h(F.value):o(F.value).then(m,E)}A((s=s.apply(n,l||[])).next())})},Xs=function(n,l){var a={label:0,sent:function(){if(h[0]&1)throw h[1];return h[1]},trys:[],ops:[]},s,o,h,v;return v={next:m(0),throw:m(1),return:m(2)},typeof Symbol=="function"&&(v[Symbol.iterator]=function(){return this}),v;function m(A){return function(F){return E([A,F])}}function E(A){if(s)throw new TypeError("Generator is already executing.");for(;a;)try{if(s=1,o&&(h=A[0]&2?o.return:A[0]?o.throw||((h=o.return)&&h.call(o),0):o.next)&&!(h=h.call(o,A[1])).done)return h;switch(o=0,h&&(A=[A[0]&2,h.value]),A[0]){case 0:case 1:h=A;break;case 4:return a.label++,{value:A[1],done:!1};case 5:a.label++,o=A[1],A=[0];continue;case 7:A=a.ops.pop(),a.trys.pop();continue;default:if(h=a.trys,!(h=h.length>0&&h[h.length-1])&&(A[0]===6||A[0]===2)){a=0;continue}if(A[0]===3&&(!h||A[1]>h[0]&&A[1]0){if(this._values.length>0)throw new Error("Illegal internal state");var a=this._settlers.shift();l instanceof Error?a.reject(l):a.resolve({value:l})}else this._values.push(l)},n.prototype.close=function(){for(;this._settlers.length>0;){var l=this._settlers.shift();l.resolve({done:!0})}this._closed=!0},n.prototype.next=function(){var l=this;if(this._values.length>0){var a=this._values.shift();return a instanceof Error?Promise.reject(a):Promise.resolve({done:!1,value:a})}if(this._closed){if(this._settlers.length>0)throw new Error("Illegal internal state");return Promise.resolve({done:!0,value:void 0})}return new Promise(function(s,o){l._settlers.push({resolve:s,reject:o})})},n}(),Sl=Io,tl=function(n,l,a,s){function o(h){return h instanceof a?h:new a(function(v){v(h)})}return new(a||(a=Promise))(function(h,v){function m(F){try{A(s.next(F))}catch(B){v(B)}}function E(F){try{A(s.throw(F))}catch(B){v(B)}}function A(F){F.done?h(F.value):o(F.value).then(m,E)}A((s=s.apply(n,l||[])).next())})},Wc=function(n,l){var a={label:0,sent:function(){if(h[0]&1)throw h[1];return h[1]},trys:[],ops:[]},s,o,h,v;return v={next:m(0),throw:m(1),return:m(2)},typeof Symbol=="function"&&(v[Symbol.iterator]=function(){return this}),v;function m(A){return function(F){return E([A,F])}}function E(A){if(s)throw new TypeError("Generator is already executing.");for(;a;)try{if(s=1,o&&(h=A[0]&2?o.return:A[0]?o.throw||((h=o.return)&&h.call(o),0):o.next)&&!(h=h.call(o,A[1])).done)return h;switch(o=0,h&&(A=[A[0]&2,h.value]),A[0]){case 0:case 1:h=A;break;case 4:return a.label++,{value:A[1],done:!1};case 5:a.label++,o=A[1],A=[0];continue;case 7:A=a.ops.pop(),a.trys.pop();continue;default:if(h=a.trys,!(h=h.length>0&&h[h.length-1])&&(A[0]===6||A[0]===2)){a=0;continue}if(A[0]===3&&(!h||A[1]>h[0]&&A[1]0&&h[h.length-1])&&(A[0]===6||A[0]===2)){a=0;continue}if(A[0]===3&&(!h||A[1]>h[0]&&A[1]0&&arguments[0]!==void 0?arguments[0]:{};(0,ct.Z)(this,n),(0,X.Z)(this,"lastFlip",-1),(0,X.Z)(this,"miter",ti.al(0,0)),(0,X.Z)(this,"started",!1),(0,X.Z)(this,"dash",!1),(0,X.Z)(this,"totalDistance",0),(0,X.Z)(this,"currentIndex",0),this.join=l.join||"miter",this.cap=l.cap||"butt",this.miterLimit=l.miterLimit||10,this.thickness=l.thickness||1,this.dash=l.dash||!1,this.complex={positions:[],indices:[],normals:[],startIndex:0,indexes:[]}}return(0,et.Z)(n,[{key:"extrude_gaode2",value:function(a,s){var o=this.complex;if(a.length<=1)return o;this.lastFlip=-1,this.started=!1,this.normal=null,this.totalDistance=0;for(var h=a.length,v=o.startIndex,m=1;mthis.miterLimit&&($n=!0)}$n?(j.push(this.normal[0],this.normal[1],0),j.push(yn[0],yn[1],0),V.push(h[0],h[1],h[2]|0,this.totalDistance,-this.thickness*cn,h[2]|0),this.complex.indexes.push(this.currentIndex),V.push(h[0],h[1],h[2]|0,this.totalDistance,this.thickness*cn,h[2]|0),this.complex.indexes.push(this.currentIndex),this.currentIndex++,B.push.apply(B,(0,me.Z)(this.lastFlip!==-cn?[s,s+2,s+3]:[s+2,s+1,s+3])),B.push(s+2,s+3,s+4),Al(Cd,Ef),ti.JG(this.normal,Cd),j.push(this.normal[0],this.normal[1],0),V.push(h[0],h[1],h[2]|0,this.totalDistance,-this.thickness*cn,h[2]|0),this.complex.indexes.push(this.currentIndex),this.currentIndex++,F+=3):(this.extrusions(V,j,h,yn,Wr,this.totalDistance),B.push.apply(B,(0,me.Z)(this.lastFlip===1?[s,s+2,s+3]:[s+2,s+1,s+3])),cn=-1,ti.JG(this.normal,yn),F+=2),this.lastFlip=cn}else{if(Al(this.normal,Zs),at){var Ye=ti.Ue(),yr=ti.Ue();ti.lu(yr,Zs,this.normal),ti.IH(Ye,Zs,this.normal),j.push(yr[0],yr[1],0),j.push(Ye[0],Ye[1],0),V.push(h[0],h[1],h[2]|0,this.totalDistance,this.thickness,h[2]|0),this.complex.indexes.push(this.currentIndex),V.push(h[0],h[1],h[2]|0,this.totalDistance,this.thickness,h[2]|0),this.complex.indexes.push(this.currentIndex),this.currentIndex++}else this.extrusions(V,j,h,this.normal,this.thickness,this.totalDistance);B.push.apply(B,(0,me.Z)(this.lastFlip===1?[s,s+2,s+3]:[s+2,s+1,s+3])),F+=2}return F}},{key:"simpleSegment",value:function(a,s,o,h,v){var m=0,E=a.indices,A=a.positions,F=a.normals,B=qo([h[0],h[1]]),V=qo([o[0],o[1]]);Lc(Zs,B,V);var j=0;if(this.dash&&(j=this.lineSegmentDistance(B,V),this.totalDistance+=j),this.normal||(this.normal=ti.Ue(),Al(this.normal,Zs)),this.started||(this.started=!0,this.extrusions(A,F,o,this.normal,this.thickness,this.totalDistance-j)),E.push(s+0,s+1,s+2),!v)Al(this.normal,Zs),this.extrusions(A,F,h,this.normal,this.thickness,this.totalDistance),E.push.apply(E,(0,me.Z)(this.lastFlip===1?[s,s+2,s+3]:[s+2,s+1,s+3])),m+=2;else{var at=qo([v[0],v[1]]);Np(B,at)&&ti.IH(at,B,ti.Fv(at,ti.$X(at,B,V))),Lc(Ef,at,B);var ut=Kc(Bp,ti.Ue(),Zs,Ef,this.thickness),xt=(0,ht.Z)(ut,2),Nt=xt[0],de=xt[1],fe=ti.AK(Bp,this.normal)<0?-1:1;this.extrusions(A,F,h,de,Nt,this.totalDistance),E.push.apply(E,(0,me.Z)(this.lastFlip===1?[s,s+2,s+3]:[s+2,s+1,s+3])),fe=-1,ti.JG(this.normal,de),m+=2,this.lastFlip=fe}return m}},{key:"segment",value:function(a,s,o,h,v){var m=0,E=a.indices,A=a.positions,F=a.normals,B=this.cap==="square",V=this.join==="bevel",j=qo([h[0],h[1]]),at=qo([o[0],o[1]]);Lc(Zs,j,at);var ut=0;if(this.dash&&(ut=this.lineSegmentDistance(j,at),this.totalDistance+=ut),this.normal||(this.normal=ti.Ue(),Al(this.normal,Zs)),!this.started)if(this.started=!0,B){var xt=ti.Ue(),Nt=ti.Ue();ti.IH(xt,this.normal,Zs),ti.IH(Nt,this.normal,Zs),F.push(Nt[0],Nt[1],0),F.push(xt[0],xt[1],0),A.push(o[0],o[1],o[2]|0,this.totalDistance-ut,-this.thickness,o[2]|0),this.complex.indexes.push(this.currentIndex),A.push(o[0],o[1],o[2]|0,this.totalDistance-ut,this.thickness,o[2]|0),this.complex.indexes.push(this.currentIndex),this.currentIndex++}else this.extrusions(A,F,o,this.normal,this.thickness,this.totalDistance-ut);if(E.push(s+0,s+1,s+2),v){var Ne=qo([v[0],v[1]]);Np(j,Ne)&&ti.IH(Ne,j,ti.Fv(Ne,ti.$X(Ne,j,at))),Lc(Ef,Ne,j);var Ye=Kc(Bp,ti.Ue(),Zs,Ef,this.thickness),yr=(0,ht.Z)(Ye,2),Ir=yr[0],Nr=yr[1],Wr=ti.AK(Bp,this.normal)<0?-1:1,yn=V;if(!yn&&this.join==="miter"){var cn=Ir;cn>this.miterLimit&&(yn=!0)}yn?(F.push(this.normal[0],this.normal[1],0),F.push(Nr[0],Nr[1],0),A.push(h[0],h[1],h[2]|0,this.totalDistance,-this.thickness*Wr,h[2]|0),this.complex.indexes.push(this.currentIndex),A.push(h[0],h[1],h[2]|0,this.totalDistance,this.thickness*Wr,h[2]|0),this.complex.indexes.push(this.currentIndex),this.currentIndex++,E.push.apply(E,(0,me.Z)(this.lastFlip!==-Wr?[s,s+2,s+3]:[s+2,s+1,s+3])),E.push(s+2,s+3,s+4),Al(Cd,Ef),ti.JG(this.normal,Cd),F.push(this.normal[0],this.normal[1],0),A.push(h[0],h[1],h[2]|0,this.totalDistance,-this.thickness*Wr,h[2]|0),this.complex.indexes.push(this.currentIndex),this.currentIndex++,m+=3):(this.extrusions(A,F,h,Nr,Ir,this.totalDistance),E.push.apply(E,(0,me.Z)(this.lastFlip===1?[s,s+2,s+3]:[s+2,s+1,s+3])),Wr=-1,ti.JG(this.normal,Nr),m+=2),this.lastFlip=Wr}else{if(Al(this.normal,Zs),B){var de=ti.Ue(),fe=ti.Ue();ti.lu(fe,Zs,this.normal),ti.IH(de,Zs,this.normal),F.push(fe[0],fe[1],0),F.push(de[0],de[1],0),A.push(h[0],h[1],h[2]|0,this.totalDistance,this.thickness,h[2]|0),this.complex.indexes.push(this.currentIndex),A.push(h[0],h[1],h[2]|0,this.totalDistance,this.thickness,h[2]|0),this.complex.indexes.push(this.currentIndex),this.currentIndex++}else this.extrusions(A,F,h,this.normal,this.thickness,this.totalDistance);E.push.apply(E,(0,me.Z)(this.lastFlip===1?[s,s+2,s+3]:[s+2,s+1,s+3])),m+=2}return m}},{key:"extrusions",value:function(a,s,o,h,v,m){s.push(h[0],h[1],0),s.push(h[0],h[1],0),a.push(o[0],o[1],o[2]|0,m,-v,o[2]|0),this.complex.indexes.push(this.currentIndex),a.push(o[0],o[1],o[2]|0,m,v,o[2]|0),this.complex.indexes.push(this.currentIndex),this.currentIndex++}},{key:"lineSegmentDistance",value:function(a,s){var o=s[0]-a[0],h=s[1]-a[1];return Math.sqrt(o*o+h*h)}}]),n}();function Vs(n){var l=n.coordinates,a=n.originCoordinates,s=n.version,o=new ov({dash:!0,join:"bevel"});if(s==="GAODE2.x"){var h=l;Array.isArray(h[0][0])||(h=[l]);var v=a;Array.isArray(v[0][0])||(v=[a]);for(var m=0;m=0;--o){var i=this.tryEntries[o],a=i.completion;if("root"===i.tryLoc)return r("end");if(i.tryLoc<=this.prev){var u=n.call(i,"catchLoc"),s=n.call(i,"finallyLoc");if(u&&s){if(this.prev=0;--r){var o=this.tryEntries[r];if(o.tryLoc<=this.prev&&n.call(o,"finallyLoc")&&this.prev=0;--e){var r=this.tryEntries[e];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),S(r),h}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.tryLoc===t){var n=r.completion;if("throw"===n.type){var o=n.arg;S(r)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,e,r){return this.delegate={iterator:k(t),resultName:e,nextLoc:r},"next"===this.method&&(this.arg=void 0),h}},t}function n(t,e,r,n,o,i,a){try{var u=t[i](a),s=u.value}catch(t){return void r(t)}u.done?e(s):Promise.resolve(s).then(n,o)}function o(t){return function(){var e=this,r=arguments;return new Promise((function(o,i){var a=t.apply(e,r);function u(t){n(a,o,i,u,s,"next",t)}function s(t){n(a,o,i,u,s,"throw",t)}u(void 0)}))}}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function a(t,e){for(var r=0;rt.length)&&(e=t.length);for(var r=0,n=new Array(e);r0&&w[0]<4?1:+(w[0]+w[1])),!E&&X&&(!(w=X.match(/Edge\\/(\\d+)/))||w[1]>=74)&&(w=X.match(/Chrome\\/(\\d+)/))&&(E=+w[1]);var nt=E,ot=!!Object.getOwnPropertySymbols&&!j((function(){var t=Symbol();return!String(t)||!(Object(t)instanceof Symbol)||!Symbol.sham&&nt&&nt<41})),it=ot&&!Symbol.sham&&"symbol"==typeof Symbol.iterator,at=I("wks"),ut=O.Symbol,st=ut&&ut.for,ct=it?ut:ut&&ut.withoutSetter||V,ft=function(t){if(!B(at,t)||!ot&&"string"!=typeof at[t]){var e="Symbol."+t;ot&&B(ut,t)?at[t]=ut[t]:at[t]=it&&st?st(e):ct(e)}return at[t]},lt={};lt[ft("toStringTag")]="z";var ht="[object z]"===String(lt),pt=!j((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]})),vt=Z.all,dt=Z.IS_HTMLDDA?function(t){return"object"==typeof t?null!==t:$(t)||t===vt}:function(t){return"object"==typeof t?null!==t:$(t)},yt=O.document,gt=dt(yt)&&dt(yt.createElement),mt=function(t){return gt?yt.createElement(t):{}},xt=!pt&&!j((function(){return 7!=Object.defineProperty(mt("div"),"a",{get:function(){return 7}}).a})),bt=pt&&j((function(){return 42!=Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype})),wt=String,Et=TypeError,At=function(t){if(dt(t))return t;throw Et(wt(t)+" is not an object")},Ot=Function.prototype.call,St=P?Ot.bind(Ot):function(){return Ot.apply(Ot,arguments)},Tt=R({}.isPrototypeOf),kt=Object,It=it?function(t){return"symbol"==typeof t}:function(t){var e=Q("Symbol");return $(e)&&Tt(e.prototype,kt(t))},jt=String,Pt=function(t){try{return jt(t)}catch(t){return"Object"}},_t=TypeError,Mt=function(t){if($(t))return t;throw _t(Pt(t)+" is not a function")},Lt=function(t,e){var r=t[e];return C(r)?void 0:Mt(r)},Rt=TypeError,Ct=TypeError,Nt=ft("toPrimitive"),Dt=function(t,e){if(!dt(t)||It(t))return t;var r,n=Lt(t,Nt);if(n){if(void 0===e&&(e="default"),r=St(n,t,e),!dt(r)||It(r))return r;throw Ct("Can't convert object to primitive value")}return void 0===e&&(e="number"),function(t,e){var r,n;if("string"===e&&$(r=t.toString)&&!dt(n=St(r,t)))return n;if($(r=t.valueOf)&&!dt(n=St(r,t)))return n;if("string"!==e&&$(r=t.toString)&&!dt(n=St(r,t)))return n;throw Rt("Can't convert object to primitive value")}(t,e)},Ft=function(t){var e=Dt(t,"string");return It(e)?e:e+""},Wt=TypeError,zt=Object.defineProperty,Bt=Object.getOwnPropertyDescriptor,Ut={f:pt?bt?function(t,e,r){if(At(t),e=Ft(e),At(r),"function"==typeof t&&"prototype"===e&&"value"in r&&"writable"in r&&!r.writable){var n=Bt(t,e);n&&n.writable&&(t[e]=r.value,r={configurable:"configurable"in r?r.configurable:n.configurable,enumerable:"enumerable"in r?r.enumerable:n.enumerable,writable:!1})}return zt(t,e,r)}:zt:function(t,e,r){if(At(t),e=Ft(e),At(r),xt)try{return zt(t,e,r)}catch(t){}if("get"in r||"set"in r)throw Wt("Accessors not supported");return"value"in r&&(t[e]=r.value),t}},Gt=Function.prototype,Ht=pt&&Object.getOwnPropertyDescriptor,Vt=B(Gt,"name"),qt={EXISTS:Vt,PROPER:Vt&&"something"===function(){}.name,CONFIGURABLE:Vt&&(!pt||pt&&Ht(Gt,"name").configurable)},Zt=R(Function.toString);$(k.inspectSource)||(k.inspectSource=function(t){return Zt(t)});var Yt,$t,Kt,Qt=k.inspectSource,Xt=O.WeakMap,Jt=$(Xt)&&/native code/.test(String(Xt)),te=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}},ee=pt?function(t,e,r){return Ut.f(t,e,te(1,r))}:function(t,e,r){return t[e]=r,t},re=I("keys"),ne=function(t){return re[t]||(re[t]=V(t))},oe={},ie=O.TypeError,ae=O.WeakMap;if(Jt||k.state){var ue=k.state||(k.state=new ae);ue.get=ue.get,ue.has=ue.has,ue.set=ue.set,Yt=function(t,e){if(ue.has(t))throw ie("Object already initialized");return e.facade=t,ue.set(t,e),e},$t=function(t){return ue.get(t)||{}},Kt=function(t){return ue.has(t)}}else{var se=ne("state");oe[se]=!0,Yt=function(t,e){if(B(t,se))throw ie("Object already initialized");return e.facade=t,ee(t,se,e),e},$t=function(t){return B(t,se)?t[se]:{}},Kt=function(t){return B(t,se)}}var ce={set:Yt,get:$t,has:Kt,enforce:function(t){return Kt(t)?$t(t):Yt(t,{})},getterFor:function(t){return function(e){var r;if(!dt(e)||(r=$t(e)).type!==t)throw ie("Incompatible receiver, "+t+" required");return r}}},fe=b((function(t){var e=qt.CONFIGURABLE,r=ce.enforce,n=ce.get,o=Object.defineProperty,i=pt&&!j((function(){return 8!==o((function(){}),"length",{value:8}).length})),a=String(String).split("String"),u=t.exports=function(t,n,u){"Symbol("===String(n).slice(0,7)&&(n="["+String(n).replace(/^Symbol\\(([^)]*)\\)/,"$1")+"]"),u&&u.getter&&(n="get "+n),u&&u.setter&&(n="set "+n),(!B(t,"name")||e&&t.name!==n)&&(pt?o(t,"name",{value:n,configurable:!0}):t.name=n),i&&u&&B(u,"arity")&&t.length!==u.arity&&o(t,"length",{value:u.arity});try{u&&B(u,"constructor")&&u.constructor?pt&&o(t,"prototype",{writable:!1}):t.prototype&&(t.prototype=void 0)}catch(t){}var s=r(t);return B(s,"source")||(s.source=a.join("string"==typeof n?n:"")),t};Function.prototype.toString=u((function(){return $(this)&&n(this).source||Qt(this)}),"toString")})),le=function(t,e,r,n){n||(n={});var o=n.enumerable,i=void 0!==n.name?n.name:e;if($(r)&&fe(r,i,n),n.global)o?t[e]=r:T(e,r);else{try{n.unsafe?t[e]&&(o=!0):delete t[e]}catch(t){}o?t[e]=r:Ut.f(t,e,{value:r,enumerable:!1,configurable:!n.nonConfigurable,writable:!n.nonWritable})}return t},he=R({}.toString),pe=R("".slice),ve=function(t){return pe(he(t),8,-1)},de=ft("toStringTag"),ye=Object,ge="Arguments"==ve(function(){return arguments}()),me=ht?ve:function(t){var e,r,n;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(r=function(t,e){try{return t[e]}catch(t){}}(e=ye(t),de))?r:ge?ve(e):"Object"==(n=ve(e))&&$(e.callee)?"Arguments":n},xe=ht?{}.toString:function(){return"[object "+me(this)+"]"};ht||le(Object.prototype,"toString",xe,{unsafe:!0});var be={}.propertyIsEnumerable,we=Object.getOwnPropertyDescriptor,Ee={f:we&&!be.call({1:2},1)?function(t){var e=we(this,t);return!!e&&e.enumerable}:be},Ae=Object,Oe=R("".split),Se=j((function(){return!Ae("z").propertyIsEnumerable(0)}))?function(t){return"String"==ve(t)?Oe(t,""):Ae(t)}:Ae,Te=function(t){return Se(D(t))},ke=Object.getOwnPropertyDescriptor,Ie={f:pt?ke:function(t,e){if(t=Te(t),e=Ft(e),xt)try{return ke(t,e)}catch(t){}if(B(t,e))return te(!St(Ee.f,t,e),t[e])}},je=Math.ceil,Pe=Math.floor,_e=Math.trunc||function(t){var e=+t;return(e>0?Pe:je)(e)},Me=function(t){var e=+t;return e!=e||0===e?0:_e(e)},Le=Math.max,Re=Math.min,Ce=function(t,e){var r=Me(t);return r<0?Le(r+e,0):Re(r,e)},Ne=Math.min,De=function(t){return t>0?Ne(Me(t),9007199254740991):0},Fe=function(t){return De(t.length)},We=function(t){return function(e,r,n){var o,i=Te(e),a=Fe(i),u=Ce(n,a);if(t&&r!=r){for(;a>u;)if((o=i[u++])!=o)return!0}else for(;a>u;u++)if((t||u in i)&&i[u]===r)return t||u||0;return!t&&-1}},ze={includes:We(!0),indexOf:We(!1)},Be=ze.indexOf,Ue=R([].push),Ge=function(t,e){var r,n=Te(t),o=0,i=[];for(r in n)!B(oe,r)&&B(n,r)&&Ue(i,r);for(;e.length>o;)B(n,r=e[o++])&&(~Be(i,r)||Ue(i,r));return i},He=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],Ve=He.concat("length","prototype"),qe={f:Object.getOwnPropertyNames||function(t){return Ge(t,Ve)}},Ze={f:Object.getOwnPropertySymbols},Ye=R([].concat),$e=Q("Reflect","ownKeys")||function(t){var e=qe.f(At(t)),r=Ze.f;return r?Ye(e,r(t)):e},Ke=function(t,e,r){for(var n=$e(e),o=Ut.f,i=Ie.f,a=0;ai;i++)if((u=g(t[i]))&&Tt(So,u))return u;return new Oo(!1)}n=wo(t,o)}for(s=h?t.next:n.next;!(c=St(s,n)).done;){try{u=g(c.value)}catch(t){Eo(n,"throw",t)}if("object"==typeof u&&u&&Tt(So,u))return u}return new Oo(!1)},ko=ft("iterator"),Io=!1;try{var jo=0,Po={next:function(){return{done:!!jo++}},return:function(){Io=!0}};Po[ko]=function(){return this},Array.from(Po,(function(){throw 2}))}catch(t){}var _o=function(t,e){if(!e&&!Io)return!1;var r=!1;try{var n={};n[ko]=function(){return{next:function(){return{done:r=!0}}}},t(n)}catch(t){}return r},Mo=Fn.CONSTRUCTOR||!_o((function(t){Mn.all(t).then(void 0,(function(){}))}));ir({target:"Promise",stat:!0,forced:Mo},{all:function(t){var e=this,r=Bn.f(e),n=r.resolve,o=r.reject,i=Tn((function(){var r=Mt(e.resolve),i=[],a=0,u=1;To(t,(function(t){var s=a++,c=!1;u++,St(r,e,t).then((function(t){c||(c=!0,i[s]=t,--u||n(i))}),o)})),--u||n(i)}));return i.error&&o(i.value),r.promise}});var Lo=Fn.CONSTRUCTOR,Ro=Mn&&Mn.prototype;if(ir({target:"Promise",proto:!0,forced:Lo,real:!0},{catch:function(t){return this.then(void 0,t)}}),$(Mn)){var Co=Q("Promise").prototype.catch;Ro.catch!==Co&&le(Ro,"catch",Co,{unsafe:!0})}ir({target:"Promise",stat:!0,forced:Mo},{race:function(t){var e=this,r=Bn.f(e),n=r.reject,o=Tn((function(){var o=Mt(e.resolve);To(t,(function(t){St(o,e,t).then(r.resolve,n)}))}));return o.error&&n(o.value),r.promise}}),ir({target:"Promise",stat:!0,forced:Fn.CONSTRUCTOR},{reject:function(t){var e=Bn.f(this);return St(e.reject,void 0,t),e.promise}});var No=Fn.CONSTRUCTOR;Q("Promise");function Do(t,e){if(!t)throw new Error(e||"web worker helper assertion failed.")}ir({target:"Promise",stat:!0,forced:No},{resolve:function(t){return function(t,e){if(At(t),dt(e)&&e.constructor===t)return e;var r=Bn.f(t);return(0,r.resolve)(e),r.promise}(this,t)}});var Fo="undefined"!=typeof window&&void 0!==window.orientation,Wo=function(){function t(t,e){var r=this;this.name=t,this.workerThread=e,this.isRunning=!0,this.resolve=function(){},this.reject=function(){},this.result=new Promise((function(t,e){r.resolve=t,r.reject=e}))}return t.prototype.postMessage=function(t,e){this.workerThread.postMessage({source:"Main thread",type:t,payload:e})},t.prototype.done=function(t){Do(this.isRunning,"WorkerJob isRunning false."),this.isRunning=!1,this.resolve(t)},t.prototype.error=function(t){Do(this.isRunning,"WorkerJob isRunning false."),this.isRunning=!1,this.reject(t)},t}(),zo=new Map;function Bo(t){Do(t.source&&!t.url||!t.source&&t.url);var e=zo.get(t.source||t.url);return e||(t.url&&(e=function(t){if(!t.startsWith("http"))return t;return Uo((e=t,"try {\\n importScripts('"+e+"');\\n} catch (error) {\\n console.error(error);\\n throw error;\\n}"));var e}(t.url),zo.set(t.url,e)),t.source&&(e=Uo(t.source),zo.set(t.source,e))),Do(e),e}function Uo(t){var e=new Blob([t],{type:"application/javascript"});return URL.createObjectURL(e)}function Go(t,e,r){void 0===e&&(e=!0);var n=r||new Set;if(t){if(Ho(t))n.add(t);else if(Ho(t.buffer))n.add(t.buffer);else if(ArrayBuffer.isView(t));else if(e&&"object"==typeof t)for(var o in t)Go(t[o],e,n)}else;return void 0===r?Array.from(n):[]}function Ho(t){return!!t&&(t instanceof ArrayBuffer||("undefined"!=typeof MessagePort&&t instanceof MessagePort||("undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap||"undefined"!=typeof OffscreenCanvas&&t instanceof OffscreenCanvas)))}var Vo=function(){},qo=function(){function t(t){this.terminated=!1,this.loadableURL="";var e=t.name,r=t.source,n=t.url;Do(r||n),this.name=e,this.source=r,this.url=n,this.onMessage=Vo,this.onError=function(t){return console.log(t)},this.worker=this.createBrowserWorker()}return t.isSupported=function(){return"undefined"!=typeof Worker},t.prototype.destroy=function(){this.onMessage=Vo,this.onError=Vo,this.worker.terminate(),this.terminated=!0},Object.defineProperty(t.prototype,"isRunning",{get:function(){return Boolean(this.onMessage)},enumerable:!1,configurable:!0}),t.prototype.postMessage=function(t,e){e=e||Go(t),this.worker.postMessage(t,e)},t.prototype.getErrorFromErrorEvent=function(t){var e="Failed to load ";return e+="worker "+this.name+" from "+this.url+". ",t.message&&(e+=t.message+" in "),t.lineno&&(e+=":"+t.lineno+":"+t.colno),new Error(e)},t.prototype.createBrowserWorker=function(){var t=this;this.loadableURL=Bo({source:this.source,url:this.url});var e=new Worker(this.loadableURL,{name:this.name});return e.onmessage=function(e){e.data?t.onMessage(e.data):t.onError(new Error("No data received"))},e.onerror=function(e){t.onError(t.getErrorFromErrorEvent(e)),t.terminated=!0},e.onmessageerror=function(e){return console.error("worker "+t.name+", message error: "+e)},e},t}(),Zo=self&&self.__assign||function(){return(Zo=Object.assign||function(t){for(var e,r=1,n=arguments.length;r0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]this.getMaxConcurrency()?(t.destroy(),this.count--):this.idleQueue.push(t),this.isDestroyed||this.startQueuedJob()},t.prototype.getAvailableWorker=function(){if(this.idleQueue.length>0)return this.idleQueue.shift()||null;if(this.count=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},Jo={maxConcurrency:3,maxMobileConcurrency:1,onDebug:function(){},reuseWorkers:!0},ti=(function(){function t(t){this.workerPools=new Map,this.props=Qo({},Jo),this.setProps(t),this.workerPools=new Map}t.isSupported=function(){return qo.isSupported()},t.getWorkerFarm=function(e){return void 0===e&&(e={}),t.workerFarm=t.workerFarm||new t({}),t.workerFarm.setProps(e),t.workerFarm},t.prototype.destroy=function(){var t,e;try{for(var r=Xo(this.workerPools.values()),n=r.next();!n.done;n=r.next()){n.value.destroy()}}catch(e){t={error:e}}finally{try{n&&!n.done&&(e=r.return)&&e.call(r)}finally{if(t)throw t.error}}},t.prototype.setProps=function(t){var e,r;this.props=Qo(Qo({},this.props),t);try{for(var n=Xo(this.workerPools.values()),o=n.next();!o.done;o=n.next()){o.value.setProps(this.getWorkerPoolProps())}}catch(t){e={error:t}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(e)throw e.error}}},t.prototype.getWorkerPool=function(t){var e=t.name,r=t.source,n=t.url,o=this.workerPools.get(e);return o||((o=new Ko({name:e,source:r,url:n})).setProps(this.getWorkerPoolProps()),this.workerPools.set(e,o)),o},t.prototype.getWorkerPoolProps=function(){return{maxConcurrency:this.props.maxConcurrency,maxMobileConcurrency:this.props.maxMobileConcurrency,reuseWorkers:this.props.reuseWorkers,onDebug:this.props.onDebug}}}(),new Map),ei=function(){function t(){}return Object.defineProperty(t,"onmessage",{set:function(t){self.onmessage=function(e){if(ri(e)){var r=e.data,n=r.type,o=r.payload;t(n,o)}}},enumerable:!1,configurable:!0}),t.addEventListener=function(t){var e=ti.get(t);e||(e=function(e){if(ri(e)){var r=e.data,n=r.type,o=r.payload;t(n,o)}}),self.addEventListener("message",e)},t.removeEventListener=function(t){var e=ti.get(t);ti.delete(t),self.removeEventListener("message",e)},t.postMessage=function(t,e){if(self){var r={source:"Worker thread",type:t,payload:e},n=Go(e);self.postMessage(r,n)}},t}();function ri(t){var e=t.type,r=t.data;return"message"===e&&r&&"string"==typeof r.source&&"Main thread"===r.source}self&&self.__awaiter,self&&self.__generator;var ni,oi,ii=function(){function t(){this._values=[],this._settlers=[],this._closed=!1}return t.prototype[Symbol.asyncIterator]=function(){return this},t.prototype.push=function(t){return this.enqueue(t)},t.prototype.enqueue=function(t){if(this._closed)throw new Error("Closed");if(this._settlers.length>0){if(this._values.length>0)throw new Error("Illegal internal state");var e=this._settlers.shift();t instanceof Error?e.reject(t):e.resolve({value:t})}else this._values.push(t)},t.prototype.close=function(){for(;this._settlers.length>0;){this._settlers.shift().resolve({done:!0})}this._closed=!0},t.prototype.next=function(){var t=this;if(this._values.length>0){var e=this._values.shift();return e instanceof Error?Promise.reject(e):Promise.resolve({done:!1,value:e})}if(this._closed){if(this._settlers.length>0)throw new Error("Illegal internal state");return Promise.resolve({done:!0,value:void 0})}return new Promise((function(e,r){t._settlers.push({resolve:e,reject:r})}))},t}(),ai=self&&self.__awaiter||function(t,e,r,n){return new(r||(r=Promise))((function(o,i){function a(t){try{s(n.next(t))}catch(t){i(t)}}function u(t){try{s(n.throw(t))}catch(t){i(t)}}function s(t){var e;t.done?o(t.value):(e=t.value,e instanceof r?e:new r((function(t){t(e)}))).then(a,u)}s((n=n.apply(t,e||[])).next())}))},ui=self&&self.__generator||function(t,e){var r,n,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function u(i){return function(u){return function(i){if(r)throw new TypeError("Generator is already executing.");for(;a;)try{if(r=1,n&&(o=2&i[0]?n.return:i[0]?n.throw||((o=n.return)&&o.call(n),0):n.next)&&!(o=o.call(n,i[1])).done)return o;switch(n=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,n=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=a.trys,(o=o.length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]m;m++)if((u||m in d)&&(p=y(h=d[m],m,v),t))if(e)b[m]=p;else if(p)switch(t){case 3:return!0;case 5:return h;case 6:return m;case 2:bi(b,h)}else switch(t){case 4:return!1;case 7:bi(b,h)}return i?-1:n||o?o:b}},Ei={forEach:wi(0),map:wi(1),filter:wi(2),some:wi(3),every:wi(4),find:wi(5),findIndex:wi(6),filterReject:wi(7)},Ai=function(t,e){var r=[][t];return!!r&&j((function(){r.call(null,e||function(){return 1},1)}))},Oi=Ei.forEach,Si=Ai("forEach")?[].forEach:function(t){return Oi(this,t,arguments.length>1?arguments[1]:void 0)},Ti=function(t){if(t&&t.forEach!==Si)try{ee(t,"forEach",Si)}catch(e){t.forEach=Si}};for(var ki in hi)hi[ki]&&Ti(O[ki]&&O[ki].prototype);Ti(di);var Ii=function(t,e,r){var n=Ft(e);n in t?Ut.f(t,n,te(0,r)):t[n]=r},ji=ft("species"),Pi=function(t){return nt>=51||!j((function(){var e=[];return(e.constructor={})[ji]=function(){return{foo:1}},1!==e[t](Boolean).foo}))},_i=Pi("slice"),Mi=ft("species"),Li=Array,Ri=Math.max;ir({target:"Array",proto:!0,forced:!_i},{slice:function(t,e){var r,n,o,i=Te(this),a=Fe(i),u=Ce(t,a),s=Ce(void 0===e?a:e,a);if(yi(i)&&(r=i.constructor,(jr(r)&&(r===Li||yi(r.prototype))||dt(r)&&null===(r=r[Mi]))&&(r=void 0),r===Li||void 0===r))return Ur(i,u,s);for(n=new(void 0===r?Li:r)(Ri(s-u,0)),o=0;u1?arguments[1]:void 0)}});var Gi,Hi=Object.keys||function(t){return Ge(t,He)},Vi={f:pt&&!bt?Object.defineProperties:function(t,e){At(t);for(var r,n=Te(e),o=Hi(e),i=o.length,a=0;i>a;)Ut.f(t,r=o[a++],n[r]);return t}},qi=ne("IE_PROTO"),Zi=function(){},Yi=function(t){return" + + \ No newline at end of file diff --git a/eiam-console/src/main/resources/fe/login-background.png b/eiam-console/src/main/resources/fe/login-background.png new file mode 100644 index 0000000000000000000000000000000000000000..1b1ec0935ca32d51aa5484154109c17d882fb473 GIT binary patch literal 1168067 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
;%xWTjWsRY>xtbuA5eu+Q-i5nA1h`a8j)NZi! zL+GNI?9}6Ost6|?oCapD8D-S1d#Y7LYN!Yg#KG?jNN2RG$3NMC@t1@r595cUT+(D> zY4_u+2)St=qiB&3v~koXEu>YXb`lsT!7TiuyIe*UOIjdj(`SjZbmoFKSF1|BK#r__^@#vzFCQL9ZQ1z zc>buP$MUjGn!Kr{xifg@-Ds^k_|g}nE+Zuk^L6p=A^A57YBWA484TfzP2&c`@M;BW zbjBYMblr|l11TxZYe|$V-A)q}!$D1Z(5j>hL)j+!SSy+1vDxbI`vp?1l3!otWetzClfC7rL+i0{RC*sy z96Tt5;-+=6>%Xc?hW_|#k;R&^lhT^}5Y1<`Ey_Y@%BvKTf4317mTTJPjM&=?K938z(Asn%r zLiE_gU@j!~EO$j-SMIOPdbS_eTuy5ZAFX^NiU?FaDlgl%s^46l*N3t#&X|0|0!LF!q!jeU8WK z%jIW{iimX8ywdE%5p&RTkgS;rsM`!@x!q5TQQ?TBjhtdYV{r4l`F-YsnJ0(>3q$&b zdx!nZuMW}|68r6FO$JiU94~)&*HVf&4-S@qKsx2jgzB1!LO27I35)SKmvRY=JS0Y& zYzAz{Ba$~=2TezXYIc=BJo9$sP@yPQ1e-@DmU(}hv7`=a@2`_;;Bo3zYAsK2?7s)b9&*ZeLNN|+b*#s_Zn3z%nR^QXU znPbwvmb^*hu?nUGNk002T{G8EDQ>8d;{%qu$`Hdvu5^8P;wF2Sj68keaiRNmuws8a&PF*#3!@@Wlpi{agHNhP6DL8S;C}aM3rAf>L|+k`O-`t z5<{ga6^ZsL53@g4#jp{8)bMJ6SM`Gm8dpxZ0(x!-X|`CGZRSnTA8|H;sHENYSj@zn z18IOK*quM>gntqQ>Gibw+R6N#cBP>%;$D{)pTKD@#8b?~kOP#AR5mfxIB9gBQ_3hg z4*XM>hjkp`muL*ncKnD??e&BmOJXmTMy4ubv-ICgRsQZ z@ClxdOZmJs0D$P9x-k-!W6Z`zh-B)tR>ScrUlzfEC%^1W)>;Z`UH8fppYTocn#kMG z0U1PCzgtU}&<2(s*O*_=bDmCEy>-_!7mt!T+L;KS^ljvL#+AMu#Upe?HD9rt>&OHj z%z4Yh09{y(kO(V?pb!}z@0tY>1(@aX*mjA{!mPkyt1LFoU6=H0FF#v*x|%L0>IZbT zcYf0Z#Z7VZ?T(JW{?j6JXPMfcDCaQ=PiCP^!+%i7n5g#bD*(sBc>b0HJK>;2$ZKtJ zw#YsI{2Qm~7LmPu zk%s)9s&6r!2F{HYDrbZSX>Q56Wf?w34peoof&MdI#AiN%UN0HuiM89 z8(rB3att*QHXa^^u>xG8m!GQYBBxa4 zPP%HRciYkRo7r4v#i}}mhdGas*>N_=N$0x7ybNoljbu|^s7J=d(Kl8iwoHQh##@K& zCf+P87GzvB`7j>4wmjzysCEXLqByDy1Y$61FsE2MUrvRPw4V^i{wiq9*WJK-+GhgV z5NBhv>H|`@(4!b;2z}(y)$o$|jgP6_0%2%v!~5vf*Auso;!ti^ z1wE`*=X*>4QQi1^=4l;vn}8X+kkOXTFZ;F9Kl+1Er20urv(WTqEd-!?V!Vy&aikSq zkoCh}2c(-<;bE7Ed*RKgpT5`rnoCIOFghRUqUy-c9EBacw$yIx05+(MxfZVtbZjNA zb&c|dL1of zRHH)22P_-1xHlme!FuKxxO; zR8s7#QEWk%87?WI7gkZ-{N3xJO0?U@D#hgxl6RbCrSzl%M|31bbw zU+nY2#`%y_YUx`d%c6$~`Js#Fte-V?uy2m)_j_*$rNUu=*OvXa4^rK;o+uTCjR&8Xy)Z%=j!nAwg^9^VBIJu-a1GW4>Yyd zH1AD?m#ykZK2Kwk@4}Q(xcSP3%7*an5-ah|k1y!sap67g;P!!Xu9h2+k>J$y#gnn6 zZTuOkvQ;_t?VP0gO3&iq`SXpBi`lQyITxXVkz)6R;OHnEVQlUBtf0jrDMSeH3uXD9x^Fk?)~nCC^f$f{EF znux1JWT9K3PWqH7jP;YAd;-<1 zz?oBt=VpxkorVX ktlpSMZyF+0>Tb588%cjsts+Wse3_dfI53~t)F*c+aA7c@qS ztA(3OPtg%@wUU=k>oeQ) zKq^qQhv+pkkrj2fM}WiGr-uYz6O7-=xXBpr@IZ0~Nl!8vb!BLI=*r@kEqriMAK85M zf4p4p(Q3nBo7nhE?~~!u?NeTpdBoVQ|2ii4@jRT@_3Hm|JIiRmP@-YmW)?%k%J616 zhvB*=TYXZ&*hM8VTxk4Sjjtv_c_B_IQ8~?A9WvVMlbHO+Es(&*_}BCjuu;uuxVMRP zM9K4nzz5sY+7guh@kYckt`XcYVs zKPV^>s>dMV~ z_DJ>w$9AiJhvO;NX0AY)RUs+yz;fa#j@$Z$5=-uL-NpY9;ySEFeQ*i8=~H?Tl=E$d z-_Z66@rfzew8%v2$HDL7rA)B9+)~LmCmz#Gar@uvtZbzpFkwI`TsO#TadS-i2CVyb z=|;s=Rou_12U)V%JH_;F5Zo-Dw+svp)qdsDYp>I7XPmHi8n76~mqE&UN=mnHh4W-XU zULN_HU7;Hf4HrqDTx5QIp!bVoKq2Yz2)w54@`&ZtjexOa>xPZZ79K_GRhWv z^LyXBe*GM7%%7iLfAUz>P|V2IfWmSb{vOI`rAQ19OOy;_SUYv#5fN#S`K!k2?nz%{ zZ{4f`@ie2yJB~_WxM)sWemNRRgpQumOfE*~Cgoz^)GXW<3sX$1-b)TNrbE?hd9BIL z4vuljhAXAaW7R&+g*}8zF$0_f&{tJ(ks;)*rFSwUgI`F3*quVWQc-zx#{f`2fxnGb z1LLBskUBBWNQ>Hl=*{hjy!Wz!SRTO2!J)&d$?;Cd_t6C$HTQog^k>qPt|_61`R04C z{vhsv95(VnNTj}Np@kj%EBvgWWH5!eUDbGv*b;1JFWEOQZOHat4?zT->#o2^Q=Pj| zhCk!W@f!nqp2t|lUHm)1b@r|an-x4=$_ZFd$Ix1>xwu-L zaLhNE`Q4TqMds1W+)kswOd;0^X+mG0U<9IpUL()HCkJs0Vu7XJa}>|gLn6b1>CN`0 z8dSLhHJp$BZ{ynLANRo@9^^ry${lgk&#tU}C-KGzg*jq`@hQx>MOM**2Ezu`{emCM zZ;!?8R{f2!fChGRcl4g3rlbU3!q$emkH=U+yw;127ozp09gr`tQ)npW63q7q%*^;* zOWeplL#wOHkZ(7sY-MKaIi8d#t#aM%7%7Y1-_mgpLvJ4P>$^YtJ#=H*F$py;tZS_KW0Jrdw35HvU& z&UTE;I>=g>sP?uP6Ha*{sHDC?&pLl*xP}9m^;om#MW*K#>%M<(yoqgH*&SzMx(Eab z(bHmDoeYwmeFg@5n7V~*q}mZnRBV=Q^kCgixEEJw51N|#J6vT~b{QK+`xlVp{ueFs z7b$IigG*c(i|}t4*K#Ff3Q;&Ax5GY|F&!4`mem9ZW1ca61wH@R7Nn!kFl5~ePRnBv z6)oy``7FP%&{BW%{N9K|b%=w?A4aCOG6SVh7|4n!h@smIB5Vc>M;`DJg}%9Xp4-jz zVV^&rmp#FQZl-svqGX>mv}%J14{x+dcC)d)vHb-?xAn?(7Ct(CgR=sN^Khx3SkEi| zW#aXWaIwKB3ofCRw`G#z-U3de+tP>6*G)KW(H+&w;XThk67#g1_ok4oVD!lBSWil|o za;KRtW_XVy53i5-f`Z{h-N!l7&*y?SD->XsOO|!76Ka)tA$9)rlIn_X$gRCCB=g&YLy0!7ZiI4TVoB>%Xd$NRT zcmThzCt&XB>+={mGxPNb^yaZTm(yGQI?4imybQ^GJty4(KTdX=roKPOjRe=&l$B!3 zo!#cPzu0R;m)(rqwApNCq~m8~M#^oefR|~wUOi8|%z}|SKc;9i96Ofy&=h`K8kEi^=#Gw@I7_XmAWu17=hakD<7!5B z+~ocivZZ<&jTVv1cmfE^S9gvxM#+u9jc8CxYN3g8D!zM_4S$vE_d~-e<#B?{f2*DD zaLf{?#(_9Mo4F85mPQ{Oz?2l6n#+5s$$up-LVb(KoTih}e}DgYoQT(+*ESWsN=rE) zh$mA>=7TSE5zNW?ic8ufMCD0uh)*iqAK|fZm&QgO6w@`;VpL%Ia3*3AG$o&4nuFcB zfMqc%M9BRgiV``CVH(8i?vikQI~ETt6cn_!m1pq9|0!cq4r2~sUL=x91iA95ip1Ko z+y3>8u8$khFVV{y-sXw2yuPp&x|ii+XD&;>a2Y>?yiFU5*A)MBU| zDK{x67b7<*+xc<1e{AaL%*~ChW^5)V;=Wkua*kBI*E}zBXd4NGg}DtwQ~!qpUnL8t zALvt}{eF3_!^MwqQec6^m26c@>D%S`YQ3SqF0_UQ{U!%VJg~#}W-L5USqt6$qpeb9Fm-u%Tfj+(R>O?s}=-wGcKw}ie#NW0(ONXXalC3er5s3BT zQ>6kpo)JdE(5<1>^V9v8^54Gj9QkBAQrmsq5?xSijPx;s#$qz`Nqm+(zs(+D7)V0c z^$s5xy5%yX8aVpc*!z&t-a7SSqZ;_*rlhw9h_X$;5P_6UCQ4hl^C(^KyrizFH4m*Q z>!9DTcKy(;gZ`t=*ap+(*aA-|wG^UWIfuNbaD?0`jHCneLys4VJ{k z9WtKog~=rI2%aI8<$EE_YP00mym7dJ#O;`@n=Jm6uLqX`s%(-uXVyI{T4D#-bOWI{ zvyd-$d>>xKE*)o}PEeVIDd-(L^G-L953~K(;rLhDRrr6?*LnG7;JovlSb6$>Zy&*Ac-LEJ_18^) z7=h2*Qdj!d!I9K&LrA!Zdtzs`q$NFXT(zOSr>hw6=g;$vljF6gga&-1au;M~0!=Wr zX1pylWU)y=%sWTd*JqL($JhPZH@J7rB^cae+f;8Egv%RMMK?0;d^q!J2e6XlSuk!v zC3BywONn9LD@F_3@xkTi+;q<)Tj{?ls3i9akUwpgqGldJU@-D|kYxB5_MF7;JJ3l5 zuR9Uts0CMhA#dwfHaQAtqS&j5u8-bGkEp)aE^>e}u#v`#2g>GNt_mLoKU>tv4L zf0g5uXu)M(|7UuK>Vbf4=mYIgmuiH;L@waXrGNmKTfT@;Mo8)Q#{g{j7UB!&VS_`e z;hYhY;c(@_z+!Rbg)t7thy{YB&q(;w?+0ZVA*lw6IE%E@EkOwJesx2jj8d%xwP0;8 zn(4nn={RDmQ~?MoNtoF}%WxEUYvP}Ago{0JjNRuJQ_^x*8*@537OEzlE>kycV_LO~ zz?Kv^^s;{D|s}N84nI;Se_u;Of`-^Jp{N$$QWa5%(Lt1RZjxWNt5gMMwiO11y zQ&!kj%G{sB+!pl%hZ3lZ`^rCr4wg;hBj`vWgAB_9j;s4g3}ZGJjUImPk%5Q+CB*dP z&u$*_LpA*7mT9+__uZGXG8^5Nhu5@-rd4cP?iH31&8CiZT^(zRsKIf^yXBh_!@7#L z=%#2nO`&SaS{6eK2m^oscbAn8bGb>8X52)ZHJ$Th6>ZVcR95?0C8@QE274{hJW3mv zL^+-WK4HS3VyMwOXmnsdsINWN2vYhLX?QO0tE&=v#hnE0d16Y7PTO#ZEA#fggEESS z(-5nJBpqb&_AyVA>LLr7Pw7Mv;38f+F$GyFZ8?xC z2xaFPK1!{#dz3HOm4i?@@!Pgvc2OBSh^Jdus^tlKt@Ii=3*2fo3Bb8oLlMGemRJ|R z7ZGbxP8O#SXV8<+H}9Y&ww8@{!=t9_Lk{9laW zkHJB=>I1)SS!m*n@2omo!?&EdGfhjKVNz=N$ulo-IIAKp*w@53Ru6Y4?q4OHtvg*D zm~_S>$tkXWa4v+)%8F8$+-w|9XZQ0bs1t^9QHF7LBnn}TsOH3%D*+0d)Q5$+BO@3T6)!XyOGimZviVV* z*dy;hc_k=oY0ux^_xkOthb9$yGmwIv6&Q-bAWa=?Qy8G@%Y~Rds3^lNar0wy4 zz3h}{gx4wZG-KeGAdQUJZpM00xhUO^(=-6bXF5!l-L~DfG0=Q_KA!(VW@!DskZC_N zA;JoQApgHj`hP6jv#ftktLzya{^72=ZQ$w$M>`tm&*XOpH#mC`m{ZS7g(-&xGS>H5 z>JK085%(KhKnfHFKDzZLY>U2J8{8-Hmcf}rn-<=#1Rf=nBnVwX6G&17>tH*jF{SlW1xRTvkBbsv2A&lRF*8PcZgh!U#CavKX_a*pcvlXliTS@b1HQ0y7JXF7mqv_P<<-9)W zK<~%KbX7vf!}H|cP2SUZ!4`W{{peBU_e#%m&3D(ds*K40m zPD%58>zI4WG|fzjRG^XT9JzI4?3p!ZH<%yK*G$XQWpA!swGxGKnwyAv%Fo+{wu%Z4 zHv|{Z-Y?#OmY}%AJuyG93?Cw8{YSC`LSz- zvx-dW?HKm2h;RW48Tq}EPv0AU;a8+uy8@5;HfiF){yj0Yiy1^J-udwfo_T& z@NcR~&P%F^i@(g!*+{^Rvc(b{`P8S-j*S_&3hQhhbUW*v4cWBRWk&u!{8D%wH@feZ zzOS6S;Ty`En{%%$ampv|Z-jQ#-rYW{VQPvwV3kT8EpElNL0LY}PihWHuH+H|^g_y# zQUD!K^I`yP@2XD?V;2??7aa5rRhyPBt>h;r8@jEtvTGNUQBCAq&QJ3JE;8fn#1&R& zOyg7n90#URCPi9y{!@C*t|oT7T3Zf!91}<9CQC683V||LDY?Dr=`Y8|$Lh7=Z@#mQ zmS4f!#LB+`0jk_%oNUtA4nQ`aqmeb)xO&1A!{)WTcS<+QXc7xGqex@>X;VBEZNzZ4 z<;k>=*LZg-j~Do@7ShX6!(jx6!wzoFQC6k@$It%H;qS+Y0fx|ZkCCoqd4q@o?k=kE z^FWx+Tx3#PT~5H|{8Qj2k#8ec-JqJM98R_r(z^>`I4GSna9_}BN>{ka zW@6g<@Y7`<=M3m)-OPY8)QWINiQC|NHzjQmg!(xIP(D7_6gckpp3CT3NP&z~{` z4ajXttywJ>K(dOX;YcMdK4aoI%=i;vTL|v5acQ)T3#3$Q>YSiU?`pWeEeLYG%rvQ6 zk+Lc)Yiiy0Jvr@F4cyHu7AOzV8k*tvqqWK~hW$ z+h;8k#}%*_Dinp>3~C`1I%-N_K@bo%MA;cxdCsKtzS^jG@);X{7&w23xcGjLdL1?P zCniNn*tzG2@2BsI?p`fzv+RhQEOsW`<^!v;n?3B%ngOT|ul>?zze_8dR_EL@9d48(k66ZUJX!q=!rg*#sZS5;_3rJrNd<61;Ju9w}ezMJsH6- z;EJI`CYS4lU1`(}HC2|leHLRpxa7R*`dkLCqtB#7K@P=QlTDD-cij$j*=6FBH zXDs}ga#&QmAq)3hy!0Op5vR0U6Zqd&M`1BBswP})XrEGl(L`L-1t+epkRZx--R0Y} zSd9yzJ7MQs`%~|BUi$7~ikqu=!GmbPE~v@LQ>SglnzLkR!eIxr>=T(ex2A zgc%qO2p8J}?AvWi%yS>LTg`9S_Blp~xwfJJ!2cKP5H@=uM=hN^gOHRbV+)9{DKS=4 zujXsgDdvPI(95s{Z}kyWH`-sggc&5M(gg?!IJt4!^z2HZe9SPpf~bY#oi`I*ignr1 zC1C9TTHkuNMo$d!RooOJZ0@LOAI-qe;AiQ}t0k4;XjhS>P;QW$bD0v(m&3KC!f+t!?=X}*P}ZTwu<;!%Q<6=Y z{v71H|8-4nQW{g6gJKqj6?CYu91F(vH#oxT6^cHQh?JyZ{%f3WoI|c(e>dV;lrqwW zY7B7~t>3llIgJ=i&1kEA|7V|4;~TVIzVXXtql`5B;FZ!m;vu?iF>_3sa~T#1Sfc zJYq9{)>OL6Ru6!;;!S!H62WZl(|OY>uxsUM(sgVgaNy@SLos(lFfZ{_!Pz^{hg$N{ z9-H1tpR)HAI4RdSbx(u)k4Kc%Jz*kwXY0>B9y@Z}M_Y}f7=@wVC)SGM%5A6zmq5tL zK$r(*X%v9nS#fv$6&;>aiUPjTRb06@vn@@(Y~2D6F4~h;oF^N!H{`jEDAS{a!cWN- zmd(wK)*Y-wv|P}gESdTF@Z{LG{ZK2EhW=G`wc9?Lw=JD@*qgciCQkLTHKA8rm0VaU zcMxX8-h(%^t}LKZkm<3O2dOKX==oVn3pLn!@aRzp#{(9y$ES{o|J02Y4x+s#DrWJE ztsm)pmugUTjM(S89zh}a9nLXciNfd8Y%pFCV33!C9x_9)L6Q3}tl^{t!$*U4K7)&PCk*&K*@l=syoLYRq7BDBNg!Zqo5w*i4Mlr3b|}~ zk%jR+^W%*RiEosQ*Eu0Fnnbb0`8kdhAq=U_R@Ks9zQk2LRmQfB*k*?VlPb*4`Lw&9 z!_sDlHiD=odji4wQ%r0(8!dyj|{Wyk+Vi;@c!?tSamVvDlOW zd7=4SyHj1KOq zVR7KB%#5MKsjO|9iG>c2M}u(ei%}c`j@6MlXN9UJa9Jcsu?JxaTvd12RgirX0ubFX zPJii0D5{CM|3*|v{P^VeNayXHnZ#XfslJ907Kd|{>F+_Yvu{?6YX~>HIeNr-W`kle z7Cs!ZO^XH_`G~Et;zn2QB>m|CC=<=yeMhfX<`UwuxlCWWB`8@kJWKf(#K@l?9j5;z z2WFq~U)PItj^11 z8Qq}Jc$JKEC#2b{LJI0Rb$r9CA4s`lLYUcj%EKEck?$y9!-%Y(1c3JC<}&*z5_A#s z;xyG4aO~#*%;Od~-Ji!Io^|lBR7P-Abp(UIztu{~8onv8?>~0dpg!*)Yn*5;f!Wn9 zd;Y%RRurMkvMlUq0oc1~^; zBGZJ-F9@fT0+nC@943B8X0A=etFE#f66uoa?zL&r>Z?fhFN+Kg!inEmyZAPP`@a4n z8%sWYM$PUMpJ&CGFP7|GAJ{t~1m+cw(H}kbt@D?g>Zk1u(`^>)sq(Q!u`Adf5Zu=N z6D7{6GAk+ri|% zVTDZd=0OmGBlEGwlQa(o1=O5XvZVeYbvxY-s?>GXX2ex{S8e-#PpfKY6Xj??1v7FH z6_(JOz+90(SOTP})bOyFr&SEDY(kl=PAOTCt#$TN18M+mUC|L0`J0YlHOEeH4MM(< z+?!hyrqqAXFh~zxObwxJIZd2@_tACm*=9AVEz>)$q?I%+6>^X`?e7x-?O3i;DYJqw zj~3U8A5_CmXyg>HMJrkXzR+oVYOvA^$f-K=EK=-c`{Xy@sS8d`veULO0AjiV)y3&gr#6y>a?SMm8Nh?i~- z;b`n>t(M*6plqO79Ji`ecH=&tem?sm-ppI{Ka>6VC-ph;g%Vw`e9?p1#dOrqcM!d$ zQ{bFBIXF1yWbH3^WT~WY`-ZYx*T+KP`Z7HNwnLjtvR;)L+o8dUw$!!T8ZfYvcow2- z6lj(XgM(4&*fK~dC@#{=EVAQjH-!}C$%P>S?;#tVk-BcenrHtMZGTI8ZGVm=G05_N z1e+DHcFkTm;+hjviy;{jM(>W8SX8W?ImTPp2lLZweT90O;0*+sLx$&#W}tTWo+Ciw z`%6U$P5v6VewTSYVz{fKW7!8irCs(*hNVmXR~wJmgU120=7*)*1`c$Fkvo)O1C4}P zMYZ7oONuM@?EPz#gYMEc%Go$zx>g!fIcWQt;L^=GO2BeQgC+}F6Z0!fBth`gHLdVF zgv%@;^B`dJRwCQ~K}6mh%7gv?#qN3JsHqaLgO%FDk(9sjWri<$=H%Z&ADC|9_KOVDTBKGOu`V@Aic4gPQ2DR~1`^>6QqnuHG zs#C9*`D+l1Qv0t#2kU!>Y09KqUf7H^wn(f|G_f{zciMPuc()y>mA}Wka&q}q?5;L% zQiH*vHBa#ScR?MOW7gkkxh#`W=?jzoy%@z2%T|xlJi361Q*SX}?Ka!-OJEEqcy{R= z0eYo$rC#?_g`yk1SZn=2O{6PU{V~hPFaIeCw?6qL!Ui6`6JmW6^8vj7aJb&vs@zt` zqH_M;#hzNI{7)aPY_{ z8dfr6Ji7BK%eup}WJ9LTI!W=$wKvEFUx?qEl{mq;gH^_9))7Yo&^!*>T;N1rMA<}7^;&V*(OecgoM3ho3#K`)i=x!Li z^deZlniP=-jDyZEK)h*<50M0JeD}df(%6upQ8T$|)_RIs z_dFyo+=IVym(@&kKy32JTplNEHI4|kwGW*1jk99@bAl|y^3L@aY8>d}USH3X<{VKk zAHaGYWK6dK8nPMhO|A&}|5PXcZ+D^J&L|3opeH&tO#PSn_1vwdTI}X`>o!VwtY}s= z9VWAK3#0O4cw#U`C;3PmP4m8a;5iw%E>vE^oO&0(ufP;2tlwYxQk`R4!Ts2vLu0$(`-uyw3Ttvl1(lfDoPPWHI8k(e>$D>qvhZV*~?Ok$NoeS?HWQ4SWMR5 z)cD*S#HUXGw6F6RGYRezmJJ9kvwHzNYN&YmPtC-&o;q;~LcXY%A2QgU+LyvLGSHNORtQK0v6f$hnGm_J>V#evke`&=E3+Q% zA*dKs{k+-BDaxrZ)}ZC}Epq`%Lkph?uT-$`Y*Sav{^fDI+nqKZKU8la#6X%I^6^;N zBs;@fG1&PcnVO=2Ar#=|{i;$HQE99d6!-`Wc3U0+4l{PMBUUj-yTR8eO?fJt?Qh{R^PnsO7BDgwsLAcF2L0RW< zVyyc}tG;Bw#RYPff>P3136@rBu}uzd;&2hwii7m@1`qv+bNt%gQyq$RW>xDq*8qCElVs{IKH06>2L=J{CzdOASXNG(*g1$4%hCY*F+tq~SfZMDCw|6(mxT{e!NqoM_x?E}J+muM4#6X!*5pn9NiD#dUds6$kJk7VuuE(;Ok!A{!|8 z%vu(G;Jj?%4d?3IXQn>lGQWLFxO_?TjdG3>R>>E zg>2c^4kg~ts$A%$tz!o>JG5TH@D$jzAVCFy@RqPjRIeyY7;QvHVhf})+RR21odv$i zY|dE~xT~^41`%m&9rgPyw40MS8Isr-dhL~V%=yID*LooYuX8+X?(?+|kS7d6xTO%U zspDvBfM}a}2JFa1L&&7y(!QT>yuP^}^>9NQ{$+7N=FT$1s+D9(j88zi?#SyzmS`G$WS%El zV>CsIU#yfJpdVm=9c3KMxLmO7)#e51K;74n-nZQ>UXA|05vS+EuZGAL?H&s8VDYLV zstU6VYHGJwd-PY!xc|~4Lv5^I_|8X*SB26}wziuaWFbU{TdmH`P19`l0C+{goHvGagglCdyDVn-QI{H0?_AZ(lE@Um- zco-M2-d|OzL345^wEJ9!zL@#cPSV8m0QD9F2S++(Y04)w-a?17cN6*{4{DG=_q|(k zgDQbl#R1k1WNd?x{7aA{`RXQ{T~g~cJ0ogs`w)=lsJaoVpik`Tn3o`DBZjh!v#X;s8=)!;v6IiBgBB z)*yYc@tI>`m7iNouF`VEPOaaV%x7$w?FSoYY>!?G!7enw7V0r%*rvmwakT5#pI_vN zrQ^Bn%{~s${I3KC^KYArToEF*g8xZQ{A&i9kvOS=$a-_<^R%G=zFu_8Bph4=K7Sgf z$j(T;s6T%6AAh@kv0=R2eEFnUQBwpVvbEB%Nu^wuVc7zaxpoOGdj_{2VCy7Dutv zmuq_b%bt8{?>}`Y@9IxxV*P3I*0=#!_3~>_Qh3SKBXk9f=Sg~LYj8bLF+#sTQhz-j zz4Y-tzW|H)Z?h8|reL6pF9go^*Wq2b2bn(^^L3^(Fcg|l;GnL@>U3I1`vIBiRGOk)9)GLW3*{^>($v~FsSB87FEGHpIdBC z93`QD`U2(8xvO{x%X05F=+%#4&k(G+$Jvc07SSF7;9<7SdRT0Ggj`R6SF5=hXqj>7 zLyV#!61&(szlOH*fASsMH$t4Mxe}>&hilx%NLU!yx=fax5!B9GEvoXR-6T)gvP}W) z=A3xMd$JzT_bGKf((deg0l3m9O6$pthXNqb29gF!`lI{oEb?%Xe3x4vbR zlU43)TTU=Bqzrn7rjzB!ha4=Tc%8^5TH4)=6C7M79M}XIK{SjuzUGCV9SjbOk{JP6 zhq(-r5I6#xDU@y7B6+IK?c(0N&+YaJiL3fdJfj%PUaf(eisy%3T(tLAXZ{rVhq38S zS>OZp*BSd$D!mArA2-A)d~|33Da;Ofx2i!iizV+AV(gD}Ft3bx0;G(=d(iN$s^Xv1 z{Y6lpaYx5Iy1iFExSs=J?eYnAh|4GYExdTf9qhmJ6#LA~BMkteKh2+dDVZxTj`B+~ zjknG=-{(WE6nOw z*X~$67IUyy*j&us-t%W8-T4Glc9u+BM#J%oUGe%h_70$xu+3`zLi3z5*TiyUH_3T5&)>s`iQLI7DO%hjVdOo(@}-j+WVl9 zFbX9e%!T=HbBFVYcZW?>yL;hTe8|gvJOj){r^m{frt^y&uf0WpJ)JSX+uQ>zD5hEH zMJj#ETAqW850gz){rjXAPGY^lfS@<6?rx-W`Ha=~*S2^nlAqyr-q;)T4~pjfiTk52 zj*$;&1P4R<_?dhywc{}x`y3wcs2nE%NSpW(kwMIEoI>8ASC}Qy&gswKPfDr^ZbK;Ck5 z-@V6=gD5{0-fpIpb50AbphZw#{FP8l3=G?l6|TiFm$i#HsWfqgs{bfG{PfEib-|*! zh5Us;W|>LXiIYB(PWvMSa(7n zw^J0a96>F$E~gQO!5%{Ar9E#5j)23 zd*W<3qgkl%acgD#`lDaZV~0zvaDRSH2-0k=z!fKHd_kZ5!D4>1I7z8}V^->tAo;4L zz}F#jMIH|bB?vnINR=h7>P`C5d5@Cy3iF{yp!PM>$LR_SXMbs}ZNb(MmXoBIs%TQc z*~q!`QPzWgj?FJ#NJTRt*)z-uNWYTjs%jDx2VH7{TuTgvR}q9+n1_RG-usxOYSzGt z3!C=p$9TqERB2fGSpCI~cWY23*P&@1jr1I|_7$36S|@ba<0S*#+&C@_*B z2f={BNg&SKc| zuHMtKeCt&`18ZR++w)G%;-y=n%oE&nFRbkV@gnqoSNLruwP&TNX}M0=ubCmnV`n%P zup_$snQ4iCVm7Cbq2lp~qQHjjfYjOZOXUxcpjj-6WY&5dS8B`wS#0sE*V{EDhHn4`SkV3^OcT!L|ZnU`FS$=qcrXdfSJ@Fo&a~3LHNfq%oTwne# z*R#x%d&Aej9p={o6|!*FC{-U6A(5l$7epnoRo6~!5l&++>_yh<`1>Xk-Y*Ba7_-UDfqY8092I!6@Eur%!7o)!(_qoZQ3A>e`1T(3WhE%jN;4 z&ezfg%aQSHgV!0ULZ^0wG*1`5H~+59v-izH?Nui-Y$%KjY*Nl@hz`oDe?4gM@rSl) z?)zG$N&c4peIYjiN8YZW$T5zi&EeSy5A^c$E)sJtk}(y{uBvPO@hUMpRrjHeDqFz^ zvHjC}v06roC&Cf*6rztS?@*y(Wqo4BE4lI76LPZYMXohFv!Lq+Nt7x*Ww7|K!;#gc zqBqsBMavqkehk&fm(@7%3_vA-X?L|uZhTXD(~GqAE9{1~t% zE9S|dVi57rlIb$;<_KWsI&9UI>9)*4ez_uU*U$uDP`phIVqLtWT+Jks#0bB-x zlxXlXBc(64!tHg8Y9brQJ%Xc{O3zD2$5~ZKF0@W0(L}nxKVXWFG<*VV!D;=NA*?U^ zkj}IIk^BO6_sfeg68mbLeX4V78%=eM*l`$pZxM;kEb_UoFy+3U+%te8-~7jg7Oel& zouXGnSu13_=_Oxd)p6<8$N&VUO6&_vMKo~<@Sfl8SjjqORpmTMO^^uj^H{Ng!di3o zlG*4U`ZXlCCn2Ixx>skmx~v&qlVO>pbi@2Fz~1&nin<#K+I{qASK!w)JmFJ>;zLCa z*1Q^(9k5X>pL^?7_|vOcKFu>JwjBGlA>}@wxiiyZ@urFSRAKXh9dV2HgwN06p? zCY^+T55q0E0b#!JdVxTIkHu>&!H;_Qpdx;T_e(5Yc7;hp zAkoA$T&Ut;eU5ze$Ew9T47YOtnxtup{2G3Ws31uVAKI1mQEU zt9xBUT9&^!vp9Rh>cv zu3dej<7{fhfuDenagaU$t8DJ5&6eHtAF5BK#`6($Uo&6BhQ?MO9q}~=YxP9CJu_6` z&y~E=CxwiS3Qh>?ovDetsSo_GA?)|Du5#H7Dw6=74ezVsU0f@EoiM{;T)Cz{3!Kyf z;ytn0fiBkDqZ07!s1+m@T+Kta!vo7>ACPzweN-klA$LX&SPC>*Z1DBuq6ob^|3Okc zJ^G%|0Cg<|=b%jMhb4v{~zCI?`LxY)YBXuNqB-auu)) z(AHOr1zpOa5Pb=(XXlPQMoBgSuSM6$a1!;}yl|pSc%P?Kc&xG5wA%vb7JVPZca| zp)J*%_>z$$D_)&~>i+f~P0ljt<%}_T8V_1=8Dk^g%;j$xecf}{Za1{ zIi%n4QcsC{gkd1N9t#HEeck8*M|{qH@_LiQ;kyAhZrI&o;Aqrmt3NpgRsXI5{^c=T z9+F%R6ZFD=!HCYCtOq$0gT-(0;u+-A@3;DUEE<8+-!>*vDmT1c`c!`s1qZ|uy`WwL5crcj^=}W;{xP{(PJyQ3VOn~y zqg#2z>&<$c(?yJEtk=M)%iobpCH5YatO3IzSozgHCM(n6# zD0lZLQ zPp%!X=M&r*u9IS5)ba+Cq0yjDUfne#-q^oDDtxKiRyxw(8(9(BJEYUPiqhnJN`#NP z9Nq}QN(*O{(}J$RMF95F5|wJgjW#|jqc9A)cug|?J#Y0rshETn^dM3OK10=P4K})% zhjq3!q$BRhbCn!t8R@07582XKH3 zfAGak$Rut!EUh$pr1pGE?Rnb%(o5YZahAfo0;;Y5;jdz`RHd0mp9vDZV~a+*`!{%j zMzwCEeX_J7?SUW|r6Ig_{<|lVGn+&OU-E{K9!{Z77gi|IcVmxRWI>f ze?p@2)&*z&BEvF!w)+!-VFh41PL;$F6}8`BfBH^_&DyOgK~9Y5cth;Ns{=;+gGV+p z>O!tr4$&~9B&^pG$>=Sg|FPQ3}PLbbp-$Bp@ZpNGogbY643*B;Tx|V{W zf|=$y%;CPQ3E*T1a?4i||4>(+m3Lg?ISiWVAXLpAi994>w|vF7_=oDZqLgNZowH(r zj5Knl_AZ4@Zym+X8~W!;lS6>Km~-3(Ly!-Y2vjt+LVS~Y##d&+IaZ>!LNwN(OozFV z#Os=~eB*Cbw`H?_YFXu$P^Qu%xXmxz$V;XR(~YK|3=xWw799~Drv~L$+0vf>fn3!w zSS}!9B?`#i@DCT($c@rkiL%tuV#xWqT)YNqF^h>i3I{Mt&}Oa=u#|jhZ%&-eRrm94 zjkZK|quyphDv^JQ+l~ta!v$Xiqv~xq)c0v-Zp24}xOS|e22q8-8(J$MWMv|A!|@mkM#ObnOTWL@)MTwFR=%d5Kg}cS>_mMqG7lVZFwYqo^gy%v}N;vS5W2eWhZgEC+1bfa!)aWx&oRf%X#7nFHm*h zDUPDoUCAS|`tEgLfG7nnG>X31@5jWS$A6w}*k6{q)C6{er65>-!8SsRGx!peaVnaP zcHkQu#l;73s2~v!xk{(o=VSi~CW)FH{Q>>gnVEr*!=qNhnq#Dvfx8kSY13BMQd=Ou z{9I#0q2RLLPm8mt+{ZZJ5n|B?n;L}V%?u4-cNDIelK1{oN+#MM1<<_N7<{G(z;&mw z#u9I$K5aaQKa>?ECrJ=!I?r=5%FXOsvfJ%(`2YwH@Q>_ccH< zPh#^_Pn7+0zM6Jpc*ZwAI4p7Uw-lV@oF^JRZz94KNsN9o0nq~(V(73xkN2{b2+?h< zGM5HtRcjB4+w#iq+i~9RmCMEe!-_R@_QpLv*j1);wD~ALPn5IP-2zV24CvzjvTP)R zj-->dRoPJ%%vjL`3edMKiTE@hIJolffON&_1lFJp6^yDLh62xf1_^v`2DDT4o-}Tu z0sNBp0pzow%5x18sK~QfG$Qy=fuy|7)xTX`lt{F<6vVkSSeWAsD{$L2D+(v7Yl)Sw zZkl3f0a*H17m6!_N3WHT55miR9Xkp(m$M$!$0t`k2OVhK_6t$P8GlCQ&!Z?QKS8&4 zx0=PP2Ci+?PHveRBSXOEnTKK=?omo;vMXyIq`#}FnyP!Tc#i`^>_j#L`wO_`Jn5N= zCqktjiYp41INp8T6*kutD4&yD&(}huWph{RC_2%cF9Xv5-giqW_gD?p{)mrL2FxJ) zt8Gt}i~lg4kkq=d@uhncx0NrEB5vVVePbh?EX{SJ)F@IRIHDuoo1CP=4LCtCXv#mh z{MU-y@1Y%3{~-`GbwIX|@P|}?I&RsU`mlwedjJtFeqmW_d6_ll%gvM=bg5k*#Kne^ z9MYN{V2QMX#YliN%zcc`nGJ|X3$!>e+21*Rka2%u6ni1=O4Re79p&yi?%nZsJrX@? z^L3u+!>QwpqY_Fl06cmYxfcrKq|xCGEr(X{1sYSsIVWY%`n{D*WVQX0qj`Q>dWonr z3fU(A8@z8Eu1n~IelJqAy72Af@Qa8zZB7Sgcdv2EhN!%CjIZP^o~T2S$!~6;J}+^v zk1B6nq_3Yk^b#PLurE9fdhGa#hmgbSYn9tQ(VTOHF6u1CO+#_=GNTdf0sPr5&y8XX z{y~b-C&dXL_8-KN;3TeS%k~uOKtB8tk?x71&~4c^@8+$e!eLPB#)~)Vsd$BTI0}IS z-XlPqP+`V@YNS|nxp_{lY|ty&{eA++xtdax$4J^x4*E#F+ls3o5o&&K)T-ehiTQ9{ zd6K}*h_4WlQ+Wa18~RhKn7`NCsnKEvbtvoFe=YJ6>7BP>u`;lwTUzO%S$dN73nnb^ z_?@IrkShq+gw;fynI;~_^Cow;Kh7#X!W8+ZV_0+f9$pMqv@YQ`v0Vl>ZkRNwrZveV z#L-OZCE#~OU8mw%gVdQ!pA$I7fh{K1{ZMqK(uRTG8K!siUe({$UPkzI^??X#DL(R_ z*cSGeI*U~%H?YedQe}vuhnz;J;#8lX=${|#J~pu4?^r;07!SPA-(%Vn9o&$kaN5+<5*oj)A2B~&`o^UTb=6{8y4(}_MpjyK=&qVw1*fA#vPmql z&p;fka_|kiyY9ywXN-gpnZh*+vm+;+V|`8u8p4CnCO6Iwl4nGpl1-UM;rT~wT{38M zY(H=TCPe5z3?az?GKsBnAcSa_tG*I)gZZF8X-|;hNU)PerAEY)28MO7$9$P^jYMhqlEe1fCGcPEmEelSm&hEXy0}cRm)0=v3Ie89pQUIMxS88 zRUzh#bdLSOr?Mmt{HbOI-Ty_^J4I(2ZQI(hZKq-<6@Rg9+qP}ntgvF+wr$(C_1AyS z*=?_N_ujvAw)u?S`xpb7KGHf0_;sqN(hz+ zJnLq*iYz0yWXMF*JqiP(bnECDrK)mXGn1=gQ|rKXc#F+M=iWeFLkyHFMmhG?0G<_% z1?Tr%e``b)SR#h3?CBT>D{CHk@zP^f&Yvh#+46&ugENUGu*tX$ht4n-k)(PVQ^ev) z*ph|;^wNmEmz|M+6ns+>*i<|D#YKfZi3O$uaC$Um7-eiuQrR(lKy98z z!#;{3id9K|0S|PBT|so-5)*rNdiGpa89&3S!|`x^Jmn6@4g`d%D#@@Yqj3?T5(w5$ z{L{ukeBW5#`&Jt4WCdAZQLTBD3_<7EIefDoMIff!%gs#>xUF9a&^7kr-9z#joR*ok z#0sWcM9C4c-~qY&FWyOnUpW6%&0Dvg3CX2Z+xjG&RKmcz^hTD$7?*!T z7Uuk%$YQ_ju>&mW1Wp)#*Z1SDCjw^q=5F(J4<#)E-QMb!f2N1hQO&2g@BgiBr(BdL zE^0sJRRlcC81@tr(K*o z(8rHI&*(zFd3bnGTrDc+5n^+dcQsv5F_r(azMXcKcNiplx<(xssAtL}&cz6zTk=ez$u%wz@DsQLey!^- zhH+}Q2};-@CK9nVJv;5v`#jzH>1>_!My;Y+-HT;bUhkld5zQF>sSLqH%A!7~MO(B* zlPa3<>#%D7Io|TNwc?-Rs&C-QUOFFIb-(gAW#!C4l?j`n=631Av}&p)m6qAaBt|vu zDk__;Nv&<8Xd0$vLsgsTN@2aJ89bTEWEzvqDdOU{K?Bi*RUHv}*aWC(u*0@gWdSNF zGMg3_du;;0lOU_6rJ6p96@ltwl08{J-nqglUowMWbQfS@6<>t z-lp@E!R%V1`mRY3BJhs1Q<`1LlkcifioZjCP_@6myzBzq!!S((K zlXUmc$A5Sw&kJL891+p$-dk_<&k=iIo&r)*#9!Q*%omxCH9E;&XF)T*jSkJ#@u%n> z^t*!lc}oKDgst18%p)=1EK^S#@5-pe0(Bg@LSmAaNN#k;#{m)!f;~*N z#$55Ev{=r4m6xlMqiCXy?s^5Uz4Iw6_rNHdYD!cc@lqOQfE|?oBRbeeiG<(AtXm>t zVB$WuR04?;QWeB7im0mqx?DxE7=e#9&T_7Z=c^~L%-a5BPfdl>jkOJHX@54V_|RRI z^-I#iM6DRRQ%5oYX$G7MUMGvYrDo;M4B_JWk^`?^mI8oAOS_S^{BQY!xt9K#SpDR& z!afvZX~v4z&kB#uE{MKicMHV2J0>!qOUv?%65Iwh{ZzGscvB1)O!4OLzJ~>ZHXncKhGROZE z>aNj|OYe{oFy23#kJ1RX+AL!p3ms}@Vg=hB{+euhKYtLsk7eF3t!^25k_dT%Bn%n; zstWWNjQcf zREd;clZ;Y0FvM5xe*=t?m`dVZ02dmJal!+4%%0cE&gTR^=ikG3%y&G9&9Xf_mNMq} zH0(IXzz9B~NJup7-E@2$Y3Gg?0KC>c%ZE^{Uad_u-4{bYqtP0Pd~MUUj7atTfni)g zj?%Is9QhBRtCPV$f;fegtuM@n!iLYrSFHiglS(5K>lt(zABVruguF~NunIZ_aWJQa zGu#q~^Mt7aH^BjK;Kys|Gdt~cn5CD6r9?q4z!&yWBl0IX-wAFcz{pHX)he0ee6}ZcF9C} zRhng}(rdA=7Hi}|-! z>)YKKoA&`k)ffnXTjozqPK=7{CEZ`v1mVcKNb3`p$)C{LX@0x?rbGC1HY$aL6DO{e zZc3F#?t6%jR=Be6ZNHpebs+B$hFlyYHbN+z_|>tI5yE!uXRrG1sb^#=0P7>Z@N9CE ziwzn=6*Wjp^PoWnD-}*1tMNX@4j8{SY`Nd8c z#jJT@<=;NGo);@c`ZOTdq0h*P2EH(9LTd+qaSF$@x?hw|#7)0+30u+`blseN>A-lT(SG4drF5s&ocgsS|#w>Mxf?Z0%d3@Yof zGA>;xEB?Ij4$-zc;c~scLh}T~L8H=bS(kPeXsn?(Fh9(+1c{^PQ0aWHjZEEG_(}^n zLnLCz!#?7W>J`4?R&qx3gffQ|4$gJ0>n8mIHrL;tqR1J;u}k0nYX2&oHmxjG`=c!qHP(1VG|S$g72%gB8$k5e^I=ClFqn$CPr-SD7&dO^>gc=)!5qm zjW1Yn8>~>7y@iEAEOW3z{&FB#ndz$f$xd+om&7{vyPFc%DPEnTF0&J_5=j&r-G(FZ zYU^#4?!T2*lsN_Nv-;%x-|P;H;rU>}b>pxcS1Fh+k3WQ}% z^VgvJ;Q|WQuxsYnRoyhdo?(~hT;%yyRf7_B%&jP*9pz^oto@hB_c9o-Gn{E$$CNfg zM2p>Lbp&=dtgFb=zG(z;0{ zYghJoO3Otp@6$YU7JI+#Fi&RK-WB`YxUWdUS6f>DlShu;-y`&H-ypl`g(F<&H)1;4 zyCbVbodeQi(*4o*a{I&i;`NB}SMQ{9Q#&8NUimWPN;YWYnPKgW)zrWMXKU5!kRJl!u%qibg-FQyz`)RyAM$Fo9Ke2bE_#G=$O0~Lm5Zg{bS z+Ias_`buSe=W^@-|7*HQd6%+atTHC0? zJey2gOQF=tI8%f5-DUN76QSGe=idq))7(JUF+S&1SOZE5hXd%R6B?Vt4FqOhrV_K6 z#pi-~*%p06+RRp_38sPhS-F_?4dvqmCgeuA0fa<&5zkDzE@b{@CKjt&n62$bLMqDQ z&|@|vVA99$AL=XEVesaE35|Q>lQ6`N7l^UDyy^92nAW#Ztxc(0hVo*6-oYtVR-2>C z*ViGw)4zs&`7)j%x+zy~bscN`&*>3DM4bOzpRbE@@SuS+%V18$9)t@HyxATQtx{t! zO|jqf(1@Gr+@?l^MMeb(kadcuZc|?>(Y9n*0}Wn882f5It%f#W5aH|&RK^^&pB;4k zDnwO#I@;d&nUz3L2yiW__omY$Cy#x2^nh|KS$L~Kz%V6(j}Kl34_6YoE`>~I0qK{2 zZdY?NPx$Lyw;m$imVtX%(BeP_d^r(8!vukbiX|o75M<+JtsOHE&UUBB&!x(PRp?*f z7>=xHYf%ZEqr(Ss-$dmBmGS9h-21DBM_ZoW0FsY9oG9|Qj4{*6brPIy+&}7baNUXh zh&*EwnZEB^ub_l2gk5PNcn{AjtJ~p|oY*HFq4N}Ec6AC1 z-}YxXe9xp^uk9Yk;GPdCWu0ae++5HxP{+kcB?H*#%M`!UX(oK1WR6ZFRJRM2@$8Sq zIF4&7x7@RHe=Zd%j?OfEc0Mv+y1nl65aoDpPZuBW05@|Q9lMBjeoAObYW<~5R$Sn( z@C-J5P{GT>`J+(t+?p5u4r)Vp)kvhLRPt4)_a8{?X3+RVtlpcmE zAMC*h!{;?*8d_8P! zdv5iXgJ|pemSVM!cgasW;*T~0)WR`lH;;=$!(STCxV zfDvC=5w(k4cvZ;jXBO%MN&eSH4J1OsKWku!mK`4o)c0CfLy4q6h*W`SUA(%*i*=-E zUSFUaT*QQ<7Mb0jy514QB@uU5=B3tFF2}g0J5mI36j|@xp9RMp^vRHnFIbHZQV8Pe zF33psB=9ateT70msj%9-z8&KzLgda3kPRtVF*1_3h;9^w&vcinfrSDEj}0J^#)k49 z{M*lFvY5+SxdKU1qLX`53VOMXuyvErb#$`k4&^AguX*yqQ!hD6-}yn^E_zqJR~ z=`i;>=<$szBDIh@tdH}ToX9f)M=rj7-15&-7Bg!v*ROQTUJPO3q!}m2KkYB2&jSg6 z6p;+nk&pejn78oe1=hMaiBRb+^$_m)#2~1xR1e&!io3B zEligfDnU>--trtJ(S3Z3h4i+m8RIftfA&M<2QSH)J(A8$HmBlbQ`-f!CVJIH)1D*( z+CqFWGu!*PbbHImFL}+Zb68(&eEk@? zM}&+S-947QZoA2FKhB*o-{hD#?MK(HkS*ldECNPlNOYv9!sJ!V4k~M;hM$0&Cj)E> zo!!Bb&_WdA<wl;zAc><@R`)xpfPk-#H51>FyH7|=?hVnUIgGvhRD-9TD(?+8ol)}r^EBb($ zD(}f^kimgg*8wfk?KW@OVv4~UN)1wJY$V&7s-#JeL2100ri_5ydKy>YyFb9Rbl9K4 zbiI@3_Dsw1iK*E-GViPY>jz-9g|ee(;l4d>w$drKq z-?_h^HTb8S`(2Dm$R01wpa!DvV(BJX(l#9u+Aq7%{x@WXpN{&0&~xBjc1eC3)jL*G z`i%eC!@OR}9#&-Tk>7`!S=07tfyfuxTiwT(>s)rp_CglN#fv2s;+EDIr&B-T zsmzgWPq14okhhuJ^YKpol|9nHAcs7;dx*zBQS5loknA0hk2$K^B*#CN(h<~OlH^2D zB93I1#ht`9lhI@sp}GTm<`4suHs=V~WqdCo?3eSwn_V5cy>bD3F45qtSbI1~4KY?X zK5&f$sebKuHyZTOXGUa-o1_jJ*hQ@W9c-WnSQAgyshphqZHaJOrO(T*nc>b+aa!Y? zrvHj1vQXFDTWk3I(Ayo!&2z#8#Oiw{QsCPEU_;ru_PXOXk!8lHAPu$8E&7^f&_CsT zA$C3Q$3$Rz?*=XG+~YUIdB2Rcec)6;SQtzwkUFbil4N^CrL^qSS=plH-W-Uf!QhT| z=jjVf^(h*fJZMp4T_h0an#;6{Uqgd&S5`pZ$@va^qM%3%u~;=3*FwLeQ2Z-&E{ts` zw0axdwvjH|qfKJl#TFyjr4MAzJ7vnm4z9ZrNYwXgYW8b>65hH$UVlDe0URlDWkJ*R z_r2y~?k4yujiIcEx-Y*Q2Et4~Tl5C55zS6)ae*1DEx=J7@5THn1KaFEr2wUc(vo%*` z&Zm|YgqY-fxc|LwXlpVBn2o4R4^T6zdJe7M`Mz;F4x2Hn7y+h0!eP7F@a`Mj@iz}y zgzweZ=C0n+z+8uSg%3}-6 z)x7(p?2ky*y-24u%EKZMiAPobD~KYEUIlS}_D0MxqXkwg^M#*j(>n%H-OJ4aYpC(g5BK%ec99dGu;Hi99=r?Iym(BB-Jvo1P6(#_3n86 zT&qj%-$b4LfMK1R9N!m;!KdJR z+!D)bTojntexynkwoI&%<}^g!p7EU}`(qQmHe=u0qs#k3d_w@lR{yI@68F$M-LjfG zYg_qj&p_Dj=RT$F*8U-*tXyW($K&}1u{rQ&l^?}X9N#eC9nw#VmjFd`P)1*AukP(H z#E7X+)8DS`tXvJi;S*v|rx&#L4FV<872Biahu?8gxMts9AGFl$jj9Tjn+M#tSlYh{ zjxy^!5iw|RI$Eh(iT+-0Kq4wBRjazYFZd4s&H;-MEEMj5lsUnr_+N+v=`#h~gT>aL z_b)Jb%NPjc+#+6!PZl}ApjP-^3n8`K3gQg15=Be;HX}{&3vbFNpJEgF)TGhI1pWbL zeMrtQlN3kUAjM9lA#^o@8QHg&6sRU~uTkzy8h;T{9gcHlCOG}ZS1*!n8Ihvl<_eLB zHdWC&TAMa*;b{QKiB5 zeka}xd*tJebjz*fAYxu!uXh;6?S=yE3}TTCg@~aQ<>A_)lek84+rNabD7u0nq%m*v z1K(NFzj1Z^Nzw9rg5Z83_C4Zoztm{8&n`jbh7TtLmaIdBFZq3r+ld)}yW80XP_sh3Jm-iVG+iqdmk06WK z3&+P5Ru%v2IBBz*7t6w)=XMdTb@KE;9w2!mT`}`X<-dV57aPvS$R- zsuzhnNTJpE?MXF{f4yCBPn6E&?Fj65NF@oxcNcGDnl4aK;fE2+LLPBmG&u-)Z()1l zdz0HlR|H)(AEDnnLMc@VH}K%OkhF(`%Ac34$;OJNYVgClh)9klGd_|yWs=Ddj zH5_5%$!-S1<0)(31RUsT>9V`aGwIp0M8S(&6pDXp`~nIGMCxSAH^<{o)2sU z{ylv_>oh^&FIBLlu2CvG%lmp2l~zpld*IYi<3n@sn#x;5Z=Md$kvof}NDhyzM23V6 zvy?bTiU2aJ#=AtTOjl-T^W%spjfc*&P24K(`=5rFVu4ir6%YNK$vCuHp>Pu2##GT3;7d1wyKO zGFO_hP#r6ip>_q%cYfbUmOGio&7@pos#B(gS5YA>9X&eKAlyR2{2W?cB7Voq3y+xI zZKKLklfe;rN)sP51s?iK5Y@V|eysnv&Ta0Y#yRi2Oi)bw?CW<-KwuknJq1NAr?STq z(ISSxyLBb%?1+#NTLX!W~bP zp`1$eS{5T=xh1r0d|rw3sLAVrAWIWNP{-=`1%kt3(5lz|pO5Cj!>dOVYI>MT*4YSx zcEh<>!irW6<5(03DX{ z)Q5XjF1^LQT&q54RkF)2WrShiOiv_dfC?Eay_s6~8ogBvL+kiT*weNziQ45KHlVg8 z`n{zn?cYSSj+5^=Ji{)wBZXKA)HbgE3?Lu*^ZELyb`8($$=q?BVZW9MMEh_K!cdVx znoq`GFFf7T()ny1LEXy+d`35;(>+DtW%x7)#@WM8YA=G4%${_=?G4V1s{e9K?Bdi0 zHWE^^XQl!uV`g06rtcHO_I-aHmOBNgZDpiui_Zg(uYS^I4p-iqB*I^`!~yQY;7>3G`c)gYbN1T$ z+_U>{#BG&cCC21~6AQa6k=n8CWrHZzs*@blQ~F%-qqPbIT+39=WL?{90gL7}t8|`t z>~R%&2*8=(;qEv}Q+N*{nN zFWQyhnh@nrF=ZA4+0~(o3(YQ%$~^2{0pdn8$faS<{Zj0DRZ|OF3UJa90-VClb&PGw z;Mrwch)|>W{5KB3RMF~LADC57kcR>83#{zcjh6$OB%I3ku3a%gj)jxki;GpyKo^_)w?a!H9`}VnW&<1#h1CGX0(|o8DUH z8|+TH3|$=W3FiK~~&%VF51CZaypeRiWZ5DVhR!pZE;m%*3bVWTJcGyfvt&2r&V z1gl!~SDu5No97HYNNTU#Y$DH?GlE>lompf`Xtq9VU?H=bNCH{&o%Y)h5ddaGRY_kC zI1Ow`=d`MKkJnTp)@2$t&XE`EQ_$$?K4?~5upr>X{_hq4pS#}Rss#+7Ru}nKed3Et zdIX2;fLC|`@Or=r(f{BX4)h>Y0=L~#V9_<#?T5HxAw)HmrgH;Z&7tj()=TS|2(K&j z7(^`c%YZR)W-O&ANosLz21PpCWQ(&5;YF+Rjj+wKp1<-~X|x*p zH&4=jrnqK)=Cn$3(>-VWoX-Ph@YsMIf7hr&h{4*6s>6yvTaCNh$Ww=g;;7Z~AOZKn zkP?|`X~;PPxOt{({K~w~uvs1O8uikHKibzfXit^hhmFG?H%M9L!!L&N5ZHW4D(`BF45M6wz|EBg zi{V?b2{O9Ozpv+T^pJ=UT`Vd8=imRoJN?|U6$^(4Sm(9Z##9d5O%a!%X^g5De5xRQnaj;;CH;#tYB5b3c>}`S+wL9glcrI*Z?_b<*ebT{ZpnjdkkmY#5Pse zlVqndUx~pAa*OKu78B6kH1SMimqX#*uoGqT+LTJ&!ZMiC>`22#z?|c*X$ERciSNkm@FX{I;^U+C9s2I6AEm0|*wSjf= z=<%@v#;e{J$j=+3%r5EZ^lMy=>8=`#OVer=MZc#icYZ41v^}vyu zR4!AcGrq0)8M+bl87a!z#mZd~&FcV%Yh73rN0%;8SyAh!1PmMVK()4s#k8szHkeUG zFo%w15sQEO%jpj@pE0Z8;kYCoM6&g@f6rLr|s=#FQ!-aCZf^+Y9mj0D&fU~_JK9p zX0IRQ;}vq%j=SFqxWhrnUPRuE+g&%FWQR0lgiWGo^OUOj9eKlN-xE6rvvCn*I@lzz zUcN-DW2O$C9X+l1MHgrY97)|Sx!y!xCk&(C=EIfu(cJ!u`obKjs%5mFCqJ*=5&zKP z_a^PsB@Q9zG{^q~g#UYjo~yP(fnrm*@g^Bf0I6^vR=W7TqV-gx)K{q1r9GMw$q*r8 zDrM&3Rmza>dO{+33T2J0F-RL`e1RJNeqkG#_FC4_?K_q^@GTyeawxv zR8!VqH14QzlxjOFD16)r2Ylpp;7}=GP}A`=a4!?!+%|tbAAC$0HWCfu)JY8xd!Wp0 zlP&^W)4eh4esRzWy1!E{)jPhX-zf~*9uI%sMtq+ZBM|YjIJXQtQHC`Mh=&PC#Sv0# z;Ex8nH25|~EU1?Z7n1!u6$V4#`;1WD(lVNeF+hKg;>HNIm+Z8a(>&1vb-IzQ@tjmN z6NM6QPlfh-KGsf2;-F9i4VdL-QRe29q?mP5N7Y+@Dk!q7j^}B7PcUA{D;OJu;@d-9 z@6&R!q3m+&%)0e09DEdQxz9IoU|3}yu8xKSGxCzz+YO(S48YIX8ME*}`s!>WEiM21 z{n+>-xBIYDS8>%*@||kj^Llikx(U%^{CIL&`Zr;fxE8`^RcRRy3#jQ6SlMz|7Y7{A zbReRIB{CbM4w#jp!m9@|M42D{h3X0YO^6yKc9>NkRZp=%T5!!VEj@|LAKDCarNyxvbUTTBMx{?-z zBT5yn3as!~{NS?eG>=un*5a~Nu0iK~_KUOfaRB#%NZ#a_iIlhi1;fZOB7NoUGlL&X z`wQvkGiIdUTUt3^ja-&LDa^FJ5%YoxugSwdt*Y-QGdK2oduH$|oQv+^PN=G}iSr@# zt$SP%e}tU{KX8ka0>-4scfdfybkKVmcrbb3&GDF2eF|3%4o^9-l%l}nCRyLlRp6)M zrU07*zi+*>?{NX^&vB6PATiV@yq1h0X)jCepIC^rdArY~2eEFZms5ru-n#=xjodOt zh8Z9Bf<9 zpL*T$op(Ww&-Ce%{$a?!MI%#>E)@wKfO_sF-d_B8yAMidJyFpdj(;DB7j-QK1rQXj zw@yAGuNf%}+MG`rWf_jl*DaZ>>s#$CTP;wA9FIhwP#V&QskQ{Gn@{utwD%^V69O@E z>Nf`%_8=;|g8d*fM3ztKHkv5QN3rJIx~)G&H&4m;bf2Nett2z*hF#>9q5tdo@v&&3 z^DiK;)2~cq1Fk8}N2_nw>j@NLNfr~$COxf1&N=BP`dur~11wV%8mTyt+ZQ0nq7H)i z=*9P~BG#dRZfQjdsoaUjx)zEWcLJ4i9wlb|$`e&STY+pvfKUU48v1OD72ISRS)_p7 z!d;K8wa$LbXWI}-eY{HZkUnUyzt6U5Qq$67AgAN>BwoRH2f=22wE-|ow8upnL6Txt zwhf0Hwvw9js3LwXi_l}RPCBnkEe$Bzb4QAA7c?B+DTXPUvk`zs? zHio6@m^B+Az6W(|dJW3ZjVp=%Fw_#H*lurCLN3aK7epAJz+SWE#qq(AF>b$gJAuN#9SItAsS$eW;s9!2lA zA|%rRUhlQ9OK}7EVy|icrX6;b=l1+HoL)z+Gp;jfPb7k2R$WE%wh1388MNajl7aeO zLmc~%gBgUMUpdxsCR%k@aB4P)9LS@GN!E56yI?)GNnYL=M{Fs@NsQQ+)Y27cc+^Y; z9ni&cx4jr3u8r6XAO}>3xrB=1i75&_)Kk@7F-2N$lu^Gti`Q@%T*2(L8sejz|5wqv zq2|iPY|qk0X|1x+(BU*qJq?SPZ%>P(NkSKEKsDsH6lnOy;?FEUF{EVq35iS9qRfp~ zfVu{9Id2198i0k4FhHnzQTXc90^w?W=3zk*(y@iG0m-qB6@u(bwpxYln_-3dy?K8Hmh40icf4o`K1r0 z7u(XBz%Y^RHXj}H3oT`_KYbmh8K8_YIM6xEP@^|tJh%}dtnXl~0(1#^22pXF(Gp!} z&_~?2?IQXw;=QBwU8AS3_gUP&K0;Z~G~b;`_ga#$fRrdBYVQu?do*&i^QOHid+KXO zHo$ls3oE{fQAGE~_WbVYTSx1Nj+zz+#qNPvBIhc&VuBloACi#tZ|nWQyD9mT@E`&i zZ73^u;sYICfyA)Uu&daSVH?vZRH*^8G&>b-Mf||F!=nb#DRtfAlEVRjK^mLA(-1SW*x?~7!7q`UgU@$+p!(;&z zU@Po9DHJkgjgzGe5h^0EJ%07Y#$JM;Ey>$K^fg%Uj7 zI|Y_uu-(WCzB6EF`DGUFH!<2=j)>KPhQ$MrQ)fBxu1xxz5>SA4=AH;u_xvRW&8`^?JdDMXgFJO8un<>>r&*P7I5(kb-_3XfRAL=d$?lG7Ko&c?8@Izr?DATTNKukT&T>QB!Nl-DXjj4B=>oOs1aOczc@zLo1(12OJ)x#$X z!2%N^CKnZ>E1|eO%~ECdfhp)}e&cK~I6dcl(O9|9)u;JT(~76m-!~qB%M$B8sxv+4 zP3GBXpmx5G~A4EFemW!dw!caYm9PHG2`r|7RmB zKmFmr9Wj;_$iv#Cv%gqQ@0z%A#<5I}YqFLnz5~QK(8-~!(2YS@9m!H+_#*Jbdv`BaFf=xWZEZ3?-M%LL&~2f zIbpsZfwx=ijF-0jQcl@_`PNF)Ib4r`pa6Qu3@eApYV9#;=WAE3U#{3+5Y{vNOYTuL zkMtxI*a?C{ea3?!g~B=q)uxe-P^LDlO3QyH;9HB;`k6@Tl~0BEY<=PD@{3GNu*%pK zE$NZ)oSGW=5?ys{)EPU%(6!C~i^#4}HxN6_5x^Cq%7Dab0D-Q!ql|&;zDkeIf2Gh= zK0mgq!EJEsaD?_ImxOtkYpo<4G7;4q4EX5|K+rP^O(E~fDLa?d%Fwh#)e+qs5R&$` z1^w1T9rZUCTx#E8+hvf{Uf{7}vsvto+cCQOY!l7LhYx%n^`^leLOz$+(Nav+Fr7=l zw_LHvI#xr@ETL2)sV{Cuy-dOTh$V=(6s*5NmKCh|JPR0K>2M)m->Wqbl(ZQbENNjo zd@AcGBf8d7;x?eu61<Tysr2BvSd+5Bq#|m$wH6blSLoKql-Vf%u|>`GYQ*t1Q}vvM`ljY|K7GtAXcFX zEfsl%qx!ct3kn(9u}5Metvi^MG%R9RMAn4aR>8EWpUJ3PnM?+6P&*BLp>0}G2SG|l zpHn&g+*>_WsHiFfdGFAFTNVi^mLQ>}s3eN($SLruYzW0lq;nybUQYtk^T@}nBU@Iw z5sxedwn`fz#yG5FesnqUgD%iBKqSN}yhAHUQ7C`7^+ z`o9S1n3O6do@J|(sQ13YcemHkg50QAte1Y-n~iQ|m;Hm3TFoEB91#uk1C>y;Kq^*h zM^^C%U4{$i!dgQQue#+9-lwb+wN+%BW_WPmCP(U$#sq!a=C_{ODJ&c@(!U+MzN!vW zz(_XQxK*^vulu%Ddx*_kq+AU}{zR^ojCW0#3n4%|Q2)h(Uq&r_{snoOiUv&DO9ZI*ibIi6L9P;(92oji=0O)mmTR!80pgo>O?K^W zV7RC6bXXO8M5jdLSxJs`o`YgbWj8HQ6^O7{vG`1umQCm>k)lO+9r)W;7S3WEaAC`T zn>0OkmoC<|?WOGZQ47@2xDBW)T>mR-B5+8 zCSi4yRbXr{vLp|L*}FfgBh2(#J&!)#27>|INq}R7#wroL@^~A+X5nYoD@HgOA7S9>%u{DOf|aE z?3$m=_Yf+L;>H|_t>u^@$w`MpFIExf95 zzCxGAok+hlU_Jc<=}QN%MS_+871b6!sU7McuGVPDzL3&JV1kWJF;F)c_zSC&GH)X= zDC{i{+u;-}H)CIE!Tb4FZq^}ln7f;zFtRj(e{=BYdxK$_lVVCYfu5t^sh2UF)1rkO zJjRl}zzaQQSB2#&O^qItA5~3HicVMjb4^;iy`g$?Yb2D+vsuNd7PadZ3lQZg`G}j$9ksaKz0dHtck%fzl6L$<-TyZh*gOtQU7v~?w?hi)xOz? zH?y2@z6asQ5j~opv7V`)doRoKsKBcpq)9v>0*55LtJ|MjiGQ>}?RYv?iRCd;Cs^x3 zC?YY48``%?U z04$^G1U&f@yYN=a$ZDZr7AF^SE_%;Q>GKP0!f=6WV!#)Pet_cU^lqBvXDAl9`#h}F zNF@b0#OjeC7(6+BxFfovD!l%elY(LGw(h%E*Od7!C2Z+2q_n4xGl@=3jR0G@i{{G? zG*^N6>ZdUsa~=W%7I^I~^`!g>ER%x4Mtq{bq(yxlEqOKML%hCY`EnKu7z`3VCHy8f zh+vUw@(Gaj-TG$I2iR6S5Pc3 zySqqd-rDbl0cEeNOz}exhc8W64|=*tM@wp6&&I8*aZXN%4J}cfoJ}(XR;90dVvI_v z==y$a+Bd0&40RM15aMHG%UZ#ya#5jtLsXqq+1kA9rwuNHF_GkBlG;&+TJc_{1MWBi zRJZJ7NW=)#k+(@A2YZa-VhQLF+#eEt^&9HokQErqHa7Ng>1tpV0tYh1BE`!AGU0ob z12)T9N%1iXQ}q*m=h5?VByjNv$pof^H*9T&YTZeU9r~K?Y+Vlos!_ac z`2BpuBKH7@h$Xa|nI240JguG(^{=`g1V8({JC^jAV#Tz#S^K?pkGXnnPo?@XFo_&O zDK3uMTI(dd^Ejh3vGs7SR^*zD3n=b{T@BuJDgR}geOU+*8U1AYI7M+#`damJbs-nB z0eQg>Q0%B3hixH4-&gr>K*8J)6C7l9?Z1LYh7U7^PX@0Lmd{wk>2@1{%~sB@1ae`f zVi{EJ4fuT?H;_;p={IH*yy`>aN3AV6s^HT|1N0gfnh+foW~g6O-cP*=jjPkC=2*s+ zB6Oe3g(lEz#zPlfDgf2+J?RokW&>o5c}gfbR8g>^FF_ptD}i{|}Mbf3wm z?DW8?GMX@yP{P@6V;Oa=nyw|YnwHplm+Q%&m5$E$V(|7)O22*)kx(gW;&i^y8gKLa zOKx7=MvqpEeca}gENSvbQ+qOW07OBK&>Wv@`P{V#NxMp+6$k^W-tA*lp;uYuyO*^% z#q@Hx?JeLpUO``T`CJJl8|%QUfa^C?8!Nr-qRiY=k~=B~Lf@S9PniYBoXHgiUV31J z1i)Jzk8j${^fiyf&@{u1f=I9$D;Y zHXm5W?_}h5XU{~_&zgUY{@ma%IlF#uh8`vY3~;Q;w5h2E7tfXqE$jBl0mkHZ$hW&x z4v)zHdT{A>S6|!&f~y9iZ*TwD}IB?jvH4-4Z2hl ztgP{`u8Loc->DK2eNMiC8@#(_z!(a{=XXIwmYZMbb2r8Z*>3KVt*jzq*{LHxgI|25 z=@*Phz^JOq*Ec>gYlmi{{Ey%Me<I|g z09gJd!k^**3z%12Db-6ktO8Os`}o@F3~>ecK;c)b^?|LhwWa~!6L`Hzn}t(J^ssf+ zKTSX6)SP81`cPAF&H5u+%gDOLsnJjgrw^vfyr_l_%>fLOyx)J-Hb{mbTFa~WyUg(d zqF0b?A1iUyvUpWyLyPy^JGbaIgGwV-Bw*U2E&C;2Kq&uh&? z+RiL@KgP~D=q9XfBsxwtZEZ;|QqPmpogBCHYz&K@Z-M=m!g*BRD8wJ#QyP;chv45c zQgrmkYR91ijpBw5@39dYNbeC=0wr$(CZQFKr*|zPinb^4zd!J9l zdHPcyGAmc@1O2F0r{wCRbRR)D|9vqLI=ya~G1)2aGc1|ZJ>+>{{lBk&{2AS^a2v7g zrG(S31tT)VOk!!$P_n3iuSviRK&dN>11Ko!xkmqA(6K*m4moyaws130fQ=VXWgp38 zlahr}0A@gW;v7Xt-O%G;|8jwLkyx1i70&cj6b$U)?R zO=TNw_mxrAHb6=o{mw-xOojQe26krVe-Tw}UYwmCtV-#griH(c(Q9;C^8toVG zmD5oO87`)2w{CKxVuteN6^LgzU#jKxb2W4OiFx);EGdhU+TmCYUts7lS0e|ro_Wn{mtH^a9 zrVO%B@3T8zh(`!GssM!3H<=!EZhP(?1|l1W1X{Y^IQSez_iOIxw0)oJ?KPvwlQwFR z5fIx|P(HSi^xeO&w#ma|uNaPq*f2pH4Si$iy7Pt3)MxN$#=i91$QNi*q-9(4V}!p2 ze_m1l76y;nteafEZBAEJQwJ)>gL0*HDN@2RAzVpWmts4Zc@gfT>4>8}?jzryi%;&_ z3VI$zuW#(5;vPP}VJ<{5dv1<}E~8X5`Q*!T@FQDquJ<9s`!Nz*NS#@NZoa#aKr;w5 zdj~)Dnnsw#EU`z9BmSdLFFy9vWB2r-&(ryteoTX{FcQ(O;rS)~F#oh@w@bg4=*jsh z!x&EddR3ceV1QJkt(1a=+9-G6eXmJ$E#Ytp%MYCCvg6ab*f!i&x1$L-5enGiEU*bR z_5wV*PD0W}Wl<=*PM&p&{ezB4#?480vOP8uP?ZOk4{zg!R0aLgX&^u1|CeP=zLG>5 zf!YD+(KQXK?87pZzaq#x+16v#75R#^C>^40yttwSZwGkfBA0m|dIDXNbZ~{MjT=}M z>uohSyZwMibp+GE7=!rgKhMrMZ?LW&&IYqQ*RXh>e=?35mBO~dZFG&B!|uD75Adv{ z@b`I8lHe|wk3v|&@ozO^vhg}t@j$*5SZO$0cwC;tZXr&za6r8a*k8n!1(Lu#oG*(hch*t)^!RlC`IKC8BxB@rk1$P8~BQF4rm- z2exSZ9M&iuCdlIm9h?&kDWyAuHRNDYMSsaQPEny!8EM}toT>4Ae-=3s&7M`?GHkI+ z6-KZ8P)`kK&qH~$Op(zR9KT1HwAAf5T<1xZ+o_NJ3GQbNx3J^`%35AuK_0y^7$;h% zt%)IODXQf}txy1YBP9PwVc2~F(!?8k1N(Jm*R#aU!esaMFd=F}tk&*x7~`APaa{z&lOM*~U^pw(rYNXYByoU)Aa3>L8PsiV8M zuTF*vG8ZRD4NXZoJgQY@3nc;mt|nHz#*9LoxcCN4yusvOjB&4Q_SafOL&xVpLU39E zONo`ClsskG&)sx72HZ$0Yon1&oWe<%B4dqo>&DPQ)V8`Qw0|s+Q4A3YFnH4Gug((6 z+^5$^y-D41UK`-F3FT=d^He59VwMsswALw8?*zEL^4L1 zh6>+fv(0YB=-#mj8VmQj{kZ!wPm{jDpU&ZO9|KkiiS7$9qsFIc@4qwd3q&u zg`Iz@klJ4sa=m%JFOlzbd(?i1zS*98+)GODXSw0;Zt&1cZs;oFUF4gT-Y2KevFrqM zvxa6{4hc?%@p7Wd-)Z4Fbb^OobkAl)-ImeƊP!-0v*v&B)5%0YXmc;O(X3&GFD z)r;Xx!idv5DZN4|s<>2l5bXS4z#!HA^F^^{Gk`YhQgSfVFyi!(gt$>SdLP*J5)RW` zQlkZ*4J&vRnw0IB2QLTDB%a3~o+rM$n$W{LUF$RjeRO#GDd zDwzxJyDj|8aDK-)=B{@Om%X(M`kymy^zW~yhS1f~WpgxAfuRjX$Yy1gg8Vy9d`Lj!KFhN0Y}aIH03lRZBZ`x3eR1yRZ z5JqI!;o^TMTcWs0jzkJu;iPW3NO!&@@0TQxY=1KMV|!A1?J;NZ$e}{JG3cT;+H)Fd z0?B8?ciy!=6=mbB((PHHMq&MqVFqR>4b5Y|{-z(9?HwEo^Wvb{>Yl@g7AMVzA8Z+l zB85UzNJh6U)pcqsE`otFBOwLY_#LeFG2Z)kIP@M_Exut?vRH)E$*EpEY4Tuf%F#m* zl!?8{Cd}oBS6W=4w~(B(`N4^;nz3rAS9!J6xuo^bQ_~g5zaBjUvChj;dWZTB;ynTBiZ8-Z7}#(89I4+{1scbzm+mT!wk#XY!y5 z^oudyhF6{4b_Nyo#EaH;mhQB-ArvJUsg|^Ro2q>yh{8$hR^Vtc5H=@;rtDja-{1IBH(h}6E7+c==`Q2 z28rT5{yI6_`QdzRE}fYDQGPSsp{+H3{}Yr*JEITpHd$b#9*&N7cUko+%SV;#A?LH} z<8GxP_s`BODw;*H?AicwBl-yHqnys6x|%P*-p5@+Znb8|_bj$NiecxvPPA^|W&b?Y=n|a%lgU zI#c8Ww8{jM%aJ?F&S!t>8fF1<*I1I$9d^&d&7pylhT-=9w^}WAFXu8aE^RGTzSd?8 z8N2~EZOJeYVj?)M?v`3>;d;{5>v66sI#8kbt^*Q&53w2Taa~_ax2N^n9UoF&j?7J8 z7XuGA#3;|HVod*p(E#zibhf6NWgg35J%TN0$G-HzeOoUez89UlU|UpOj~cH%X;#EN zD{z|bQJY@*1Qw%a;nFRm8ylyPQA;R(Yw@C4m5YIN`kf`7}EpI$<(~q-F#UO@bYc0V!y2@v=s|I9{_hYAd+7Qj%v{Pc~2Ga2*+)C&t4Fn_WyLYG|sw_5}1lWva zExpC4AY9%;2%yh-jVMAz>l`QSdmkSqIXu!&1&LZaxsar%|1C(J5pz_}xf*gD&JJ)6 zR=dHF2+Xg8EBhT|z_HwxTWLFKlLPaX;+C9w(Lie1?1qDw*Pn`Ke*?=o^A{pdMSDi9 zL8$uK>MH)2fvyS&I=p-mIB|UZAUn_&uJ`HkiRJgc9xTFBxZ#zJCU7woDzlwO?8mOn%R=wUjH6B+Y^dvvRpW*rKnFrjj#AI zQ`8H$LqK`J6aAu}t`A%3uRoh;^^%1jubw+*&=CFs{023apUMQF2F)r?BR=V~g*3~- zgR$dLO#c9^lX-lqe5TyhaQxq%Cm9_dv+m>L(4G+N8f=J%!i-&lzg`aU60zz6B_(Rf z`?d?{ra(ZDkvPzn3&_^#uGPuu{IagMrH_w&TU%PsBXJk;G OwUC^Sz*z&Dn13dx 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%0DC81FfN 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�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

HJ+$0M`#9H{x(myi6=PJ}&gyhMI#yL4K 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