├── .editorconfig ├── .gitignore ├── IdentityServer4.MongoDB.sln ├── LICENSE ├── README.md ├── UPDATE.md ├── appveyor.yml ├── build.cake ├── build.ps1 ├── build.sh ├── global.json ├── src ├── Host │ ├── Configuration │ │ ├── Clients.cs │ │ └── Resources.cs │ ├── Extensions │ │ ├── ExtensionGrantValidator.cs │ │ └── NoSubjectExtensionGrantValidator.cs │ ├── Host.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Quickstart │ │ ├── Account │ │ │ ├── AccountController.cs │ │ │ ├── AccountOptions.cs │ │ │ ├── AccountService.cs │ │ │ ├── ExternalProvider.cs │ │ │ ├── LoggedOutViewModel.cs │ │ │ ├── LoginInputModel.cs │ │ │ ├── LoginViewModel.cs │ │ │ ├── LogoutInputModel.cs │ │ │ └── LogoutViewModel.cs │ │ ├── Consent │ │ │ ├── ConsentController.cs │ │ │ ├── ConsentInputModel.cs │ │ │ ├── ConsentOptions.cs │ │ │ ├── ConsentService.cs │ │ │ ├── ConsentViewModel.cs │ │ │ ├── ProcessConsentResult.cs │ │ │ └── ScopeViewModel.cs │ │ ├── Grants │ │ │ ├── GrantsController.cs │ │ │ └── GrantsViewModel.cs │ │ ├── Home │ │ │ ├── ErrorViewModel.cs │ │ │ └── HomeController.cs │ │ ├── SecurityHeadersAttribute.cs │ │ └── TestUsers.cs │ ├── Startup.cs │ ├── Views │ │ ├── Account │ │ │ ├── LoggedOut.cshtml │ │ │ ├── Login.cshtml │ │ │ └── Logout.cshtml │ │ ├── Consent │ │ │ ├── Index.cshtml │ │ │ └── _ScopeListItem.cshtml │ │ ├── Grants │ │ │ └── Index.cshtml │ │ ├── Home │ │ │ └── Index.cshtml │ │ ├── Shared │ │ │ ├── Error.cshtml │ │ │ ├── _Layout.cshtml │ │ │ └── _ValidationSummary.cshtml │ │ ├── _ViewImports.cshtml │ │ └── _ViewStart.cshtml │ ├── appsettings.json │ ├── web.config │ └── wwwroot │ │ ├── css │ │ ├── site.css │ │ ├── site.less │ │ └── site.min.css │ │ ├── favicon.ico │ │ ├── icon.jpg │ │ ├── icon.png │ │ ├── js │ │ └── signout-redirect.js │ │ └── lib │ │ ├── bootstrap │ │ ├── css │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.css.map │ │ │ └── bootstrap.min.css │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ │ └── js │ │ │ ├── bootstrap.js │ │ │ └── bootstrap.min.js │ │ └── jquery │ │ ├── jquery.js │ │ ├── jquery.min.js │ │ └── jquery.min.map └── IdentityServer4.MongoDB │ ├── Configuration │ └── MongoDBConfiguration.cs │ ├── Constants.cs │ ├── DbContexts │ ├── ConfigurationDbContext.cs │ ├── MongoDBContextBase.cs │ └── PersistedGrantDbContext.cs │ ├── Entities │ ├── ApiResource.cs │ ├── ApiResourceClaim.cs │ ├── ApiScope.cs │ ├── ApiScopeClaim.cs │ ├── ApiSecret.cs │ ├── Client.cs │ ├── ClientClaim.cs │ ├── ClientCorsOrigin.cs │ ├── ClientGrantType.cs │ ├── ClientIdPRestriction.cs │ ├── ClientPostLogoutRedirectUri.cs │ ├── ClientProperty.cs │ ├── ClientRedirectUri.cs │ ├── ClientScope.cs │ ├── ClientSecret.cs │ ├── IdentityClaim.cs │ ├── IdentityResource.cs │ ├── PersistedGrant.cs │ ├── Secret.cs │ └── UserClaim.cs │ ├── Extensions │ └── IdentityServerMongoDBBuilderExtensions.cs │ ├── IdentityServer4.MongoDB.csproj │ ├── Interfaces │ ├── IConfigurationDbContext.cs │ └── IPersistedGrantDbContext.cs │ ├── Mappers │ ├── ApiResourceMapperProfile.cs │ ├── ApiResourceMappers.cs │ ├── ApiScopeMapperProfile.cs │ ├── ApiScopeMappers.cs │ ├── ClientMapperProfile.cs │ ├── ClientMappers.cs │ ├── IdentityResourceMapperProfile.cs │ ├── IdentityResourceMappers.cs │ ├── PersistedGrantMapperProfile.cs │ └── PersistedGrantMappers.cs │ ├── Options │ └── TokenCleanupOptions.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── Services │ └── CorsPolicyService.cs │ ├── Stores │ ├── ClientStore.cs │ ├── PersistedGrantStore.cs │ └── ResourceStore.cs │ ├── TokenCleanup.cs │ └── TokenCleanupService.cs ├── test └── IdentityServer4.Tests.MongoDB │ ├── IdentityServer4.Tests.MongoDB.csproj │ └── Mappers │ ├── ApiResourceMappersTest.cs │ ├── ApiScopeMappersTest.cs │ ├── ClientMappersTest.cs │ └── IdentityResourceMappersTest.cs └── tools └── packages.config /.editorconfig: -------------------------------------------------------------------------------- 1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories 2 | root = true 3 | 4 | # C# files 5 | [*.cs] 6 | 7 | #### Core EditorConfig Options #### 8 | 9 | # Indentation and spacing 10 | indent_size = 4 11 | indent_style = space 12 | tab_width = 4 13 | 14 | # New line preferences 15 | end_of_line = crlf 16 | insert_final_newline = false 17 | 18 | #### .NET Coding Conventions #### 19 | 20 | # this. and Me. preferences 21 | dotnet_style_qualification_for_event = false:warning 22 | dotnet_style_qualification_for_field = false:warning 23 | dotnet_style_qualification_for_method = false:warning 24 | dotnet_style_qualification_for_property = false:warning 25 | 26 | # Language keywords vs BCL types preferences 27 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent 28 | dotnet_style_predefined_type_for_member_access = true:silent 29 | 30 | # Parentheses preferences 31 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 32 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 33 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 34 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 35 | 36 | # Modifier preferences 37 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent 38 | 39 | # Expression-level preferences 40 | csharp_style_deconstructed_variable_declaration = true:suggestion 41 | csharp_style_inlined_variable_declaration = true:suggestion 42 | csharp_style_throw_expression = true:suggestion 43 | dotnet_style_coalesce_expression = true:suggestion 44 | dotnet_style_collection_initializer = true:suggestion 45 | dotnet_style_explicit_tuple_names = true:suggestion 46 | dotnet_style_null_propagation = true:suggestion 47 | dotnet_style_object_initializer = true:suggestion 48 | dotnet_style_prefer_auto_properties = true:silent 49 | dotnet_style_prefer_compound_assignment = true:suggestion 50 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 51 | dotnet_style_prefer_conditional_expression_over_return = true:silent 52 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 53 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 54 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 55 | 56 | # Field preferences 57 | dotnet_style_readonly_field = true:suggestion 58 | 59 | # Parameter preferences 60 | dotnet_code_quality_unused_parameters = all:suggestion 61 | 62 | #### C# Coding Conventions #### 63 | 64 | # var preferences 65 | csharp_style_var_elsewhere = false:silent 66 | csharp_style_var_for_built_in_types = false:silent 67 | csharp_style_var_when_type_is_apparent = false:silent 68 | 69 | # Expression-bodied members 70 | csharp_style_expression_bodied_accessors = true:silent 71 | csharp_style_expression_bodied_constructors = false:silent 72 | csharp_style_expression_bodied_indexers = true:silent 73 | csharp_style_expression_bodied_lambdas = true:silent 74 | csharp_style_expression_bodied_local_functions = false:silent 75 | csharp_style_expression_bodied_methods = false:silent 76 | csharp_style_expression_bodied_operators = false:silent 77 | csharp_style_expression_bodied_properties = true:silent 78 | 79 | # Pattern matching preferences 80 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 81 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 82 | 83 | # Null-checking preferences 84 | csharp_style_conditional_delegate_call = true:suggestion 85 | 86 | # Modifier preferences 87 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async 88 | 89 | # Code-block preferences 90 | csharp_prefer_braces = true:silent 91 | 92 | # Expression-level preferences 93 | csharp_prefer_simple_default_expression = true:suggestion 94 | csharp_style_pattern_local_over_anonymous_function = true:suggestion 95 | csharp_style_prefer_index_operator = true:suggestion 96 | csharp_style_prefer_range_operator = true:suggestion 97 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion 98 | csharp_style_unused_value_expression_statement_preference = discard_variable:silent 99 | 100 | #### C# Formatting Rules #### 101 | 102 | # New line preferences 103 | csharp_new_line_before_catch = true 104 | csharp_new_line_before_else = true 105 | csharp_new_line_before_finally = true 106 | csharp_new_line_before_members_in_anonymous_types = true 107 | csharp_new_line_before_members_in_object_initializers = true 108 | csharp_new_line_before_open_brace = all 109 | csharp_new_line_between_query_expression_clauses = true 110 | 111 | # Indentation preferences 112 | csharp_indent_block_contents = true 113 | csharp_indent_braces = false 114 | csharp_indent_case_contents = true 115 | csharp_indent_case_contents_when_block = true 116 | csharp_indent_labels = one_less_than_current 117 | csharp_indent_switch_labels = true 118 | 119 | # Space preferences 120 | csharp_space_after_cast = false 121 | csharp_space_after_colon_in_inheritance_clause = true 122 | csharp_space_after_comma = true 123 | csharp_space_after_dot = false 124 | csharp_space_after_keywords_in_control_flow_statements = true 125 | csharp_space_after_semicolon_in_for_statement = true 126 | csharp_space_around_binary_operators = before_and_after 127 | csharp_space_around_declaration_statements = false 128 | csharp_space_before_colon_in_inheritance_clause = true 129 | csharp_space_before_comma = false 130 | csharp_space_before_dot = false 131 | csharp_space_before_open_square_brackets = false 132 | csharp_space_before_semicolon_in_for_statement = false 133 | csharp_space_between_empty_square_brackets = false 134 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 135 | csharp_space_between_method_call_name_and_opening_parenthesis = false 136 | csharp_space_between_method_call_parameter_list_parentheses = false 137 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 138 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 139 | csharp_space_between_method_declaration_parameter_list_parentheses = false 140 | csharp_space_between_parentheses = false 141 | csharp_space_between_square_brackets = false 142 | 143 | # Wrapping preferences 144 | csharp_preserve_single_line_blocks = true 145 | csharp_preserve_single_line_statements = true 146 | 147 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # IdentityServer4-specific files 5 | identityserver4_log.txt 6 | tempkey.rsa 7 | tempkey.jwk 8 | 9 | # User-specific files 10 | *.suo 11 | *.user 12 | *.userosscache 13 | *.sln.docstates 14 | 15 | # User-specific files (MonoDevelop/Xamarin Studio) 16 | *.userprefs 17 | 18 | # Build results 19 | [Dd]ebug/ 20 | [Dd]ebugPublic/ 21 | [Rr]elease/ 22 | [Rr]eleases/ 23 | x64/ 24 | x86/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015 cache/options directory 31 | .vs/ 32 | # Visual Studio Code cache/options directory 33 | .vscode/ 34 | # Uncomment if you have tasks that create the project's static files in wwwroot 35 | #wwwroot/ 36 | 37 | # MSTest test Results 38 | [Tt]est[Rr]esult*/ 39 | [Bb]uild[Ll]og.* 40 | 41 | # NUNIT 42 | *.VisualState.xml 43 | TestResult.xml 44 | 45 | # Build Results of an ATL Project 46 | [Dd]ebugPS/ 47 | [Rr]eleasePS/ 48 | dlldata.c 49 | 50 | # DNX 51 | project.lock.json 52 | project.fragment.lock.json 53 | artifacts/ 54 | 55 | *_i.c 56 | *_p.c 57 | *_i.h 58 | *.ilk 59 | *.meta 60 | *.obj 61 | *.pch 62 | *.pdb 63 | *.pgc 64 | *.pgd 65 | *.rsp 66 | *.sbr 67 | *.tlb 68 | *.tli 69 | *.tlh 70 | *.tmp 71 | *.tmp_proj 72 | *.log 73 | *.vspscc 74 | *.vssscc 75 | .builds 76 | *.pidb 77 | *.svclog 78 | *.scc 79 | 80 | # Chutzpah Test files 81 | _Chutzpah* 82 | 83 | # Visual C++ cache files 84 | ipch/ 85 | *.aps 86 | *.ncb 87 | *.opendb 88 | *.opensdf 89 | *.sdf 90 | *.cachefile 91 | *.VC.db 92 | *.VC.VC.opendb 93 | 94 | # Visual Studio profiler 95 | *.psess 96 | *.vsp 97 | *.vspx 98 | *.sap 99 | 100 | # TFS 2012 Local Workspace 101 | $tf/ 102 | 103 | # Guidance Automation Toolkit 104 | *.gpState 105 | 106 | # ReSharper is a .NET coding add-in 107 | _ReSharper*/ 108 | *.[Rr]e[Ss]harper 109 | *.DotSettings.user 110 | 111 | # JustCode is a .NET coding add-in 112 | .JustCode 113 | 114 | # TeamCity is a build add-in 115 | _TeamCity* 116 | 117 | # DotCover is a Code Coverage Tool 118 | *.dotCover 119 | 120 | # Visual Studio code coverage results 121 | *.coverage 122 | *.coveragexml 123 | 124 | # NCrunch 125 | _NCrunch_* 126 | .*crunch*.local.xml 127 | nCrunchTemp_* 128 | 129 | # MightyMoose 130 | *.mm.* 131 | AutoTest.Net/ 132 | 133 | # Web workbench (sass) 134 | .sass-cache/ 135 | 136 | # Installshield output folder 137 | [Ee]xpress/ 138 | 139 | # DocProject is a documentation generator add-in 140 | DocProject/buildhelp/ 141 | DocProject/Help/*.HxT 142 | DocProject/Help/*.HxC 143 | DocProject/Help/*.hhc 144 | DocProject/Help/*.hhk 145 | DocProject/Help/*.hhp 146 | DocProject/Help/Html2 147 | DocProject/Help/html 148 | 149 | # Click-Once directory 150 | publish/ 151 | 152 | # Publish Web Output 153 | *.[Pp]ublish.xml 154 | *.azurePubxml 155 | # TODO: Comment the next line if you want to checkin your web deploy settings 156 | # but database connection strings (with potential passwords) will be unencrypted 157 | *.pubxml 158 | *.publishproj 159 | 160 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 161 | # checkin your Azure Web App publish settings, but sensitive information contained 162 | # in these scripts will be unencrypted 163 | PublishScripts/ 164 | 165 | # NuGet Packages 166 | *.nupkg 167 | # The packages folder can be ignored because of Package Restore 168 | **/packages/* 169 | # except build/, which is used as an MSBuild target. 170 | !**/packages/build/ 171 | # Uncomment if necessary however generally it will be regenerated when needed 172 | #!**/packages/repositories.config 173 | # NuGet v3's project.json files produces more ignoreable files 174 | *.nuget.props 175 | *.nuget.targets 176 | 177 | # Microsoft Azure Build Output 178 | csx/ 179 | *.build.csdef 180 | 181 | # Microsoft Azure Emulator 182 | ecf/ 183 | rcf/ 184 | 185 | # Windows Store app package directories and files 186 | AppPackages/ 187 | BundleArtifacts/ 188 | Package.StoreAssociation.xml 189 | _pkginfo.txt 190 | 191 | # Visual Studio cache files 192 | # files ending in .cache can be ignored 193 | *.[Cc]ache 194 | # but keep track of directories ending in .cache 195 | !*.[Cc]ache/ 196 | 197 | # Others 198 | ClientBin/ 199 | ~$* 200 | *~ 201 | *.dbmdl 202 | *.dbproj.schemaview 203 | *.jfm 204 | *.pfx 205 | *.publishsettings 206 | node_modules/ 207 | orleans.codegen.cs 208 | 209 | # Since there are multiple workflows, uncomment next line to ignore bower_components 210 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 211 | #bower_components/ 212 | 213 | # RIA/Silverlight projects 214 | Generated_Code/ 215 | 216 | # Backup & report files from converting an old project file 217 | # to a newer Visual Studio version. Backup files are not needed, 218 | # because we have git ;-) 219 | _UpgradeReport_Files/ 220 | Backup*/ 221 | UpgradeLog*.XML 222 | UpgradeLog*.htm 223 | 224 | # SQL Server files 225 | *.mdf 226 | *.ldf 227 | 228 | # Business Intelligence projects 229 | *.rdl.data 230 | *.bim.layout 231 | *.bim_*.settings 232 | 233 | # Microsoft Fakes 234 | FakesAssemblies/ 235 | 236 | # GhostDoc plugin setting file 237 | *.GhostDoc.xml 238 | 239 | # Node.js Tools for Visual Studio 240 | .ntvs_analysis.dat 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio LightSwitch build output 249 | **/*.HTMLClient/GeneratedArtifacts 250 | **/*.DesktopClient/GeneratedArtifacts 251 | **/*.DesktopClient/ModelManifest.xml 252 | **/*.Server/GeneratedArtifacts 253 | **/*.Server/ModelManifest.xml 254 | _Pvt_Extensions 255 | 256 | # Paket dependency manager 257 | .paket/paket.exe 258 | paket-files/ 259 | 260 | # FAKE - F# Make 261 | .fake/ 262 | 263 | # JetBrains Rider 264 | .idea/ 265 | *.sln.iml 266 | 267 | # CodeRush 268 | .cr/ 269 | 270 | # Python Tools for Visual Studio (PTVS) 271 | __pycache__/ 272 | *.pyc 273 | 274 | # Cake - Uncomment if you are using it 275 | tools/**/*.exe 276 | tools/Cake/ 277 | tools/packages.config.md5sum -------------------------------------------------------------------------------- /IdentityServer4.MongoDB.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29409.12 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{1E3859B1-7876-4B22-B7B6-1049D0344E52}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{162608DF-1D19-46FD-BC24-408D199F8975}" 9 | ProjectSection(SolutionItems) = preProject 10 | global.json = global.json 11 | EndProjectSection 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Host", "src\Host\Host.csproj", "{00F607AE-4853-42A1-B536-F3B7759E1987}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentityServer4.MongoDB", "src\IdentityServer4.MongoDB\IdentityServer4.MongoDB.csproj", "{A3A275B3-6017-4C98-B1E3-451EE07EF4F4}" 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{CFAC3A63-E145-473E-9AA0-99EA17EC757D}" 18 | EndProject 19 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IdentityServer4.Tests.MongoDB", "test\IdentityServer4.Tests.MongoDB\IdentityServer4.Tests.MongoDB.csproj", "{277D3533-13CC-462E-9BA0-74BAF2658D07}" 20 | EndProject 21 | Global 22 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 23 | Debug|Any CPU = Debug|Any CPU 24 | Release|Any CPU = Release|Any CPU 25 | EndGlobalSection 26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 27 | {00F607AE-4853-42A1-B536-F3B7759E1987}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {00F607AE-4853-42A1-B536-F3B7759E1987}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {00F607AE-4853-42A1-B536-F3B7759E1987}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {00F607AE-4853-42A1-B536-F3B7759E1987}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {A3A275B3-6017-4C98-B1E3-451EE07EF4F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {A3A275B3-6017-4C98-B1E3-451EE07EF4F4}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {A3A275B3-6017-4C98-B1E3-451EE07EF4F4}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {A3A275B3-6017-4C98-B1E3-451EE07EF4F4}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {277D3533-13CC-462E-9BA0-74BAF2658D07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {277D3533-13CC-462E-9BA0-74BAF2658D07}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {277D3533-13CC-462E-9BA0-74BAF2658D07}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {277D3533-13CC-462E-9BA0-74BAF2658D07}.Release|Any CPU.Build.0 = Release|Any CPU 39 | EndGlobalSection 40 | GlobalSection(SolutionProperties) = preSolution 41 | HideSolutionNode = FALSE 42 | EndGlobalSection 43 | GlobalSection(NestedProjects) = preSolution 44 | {00F607AE-4853-42A1-B536-F3B7759E1987} = {1E3859B1-7876-4B22-B7B6-1049D0344E52} 45 | {A3A275B3-6017-4C98-B1E3-451EE07EF4F4} = {1E3859B1-7876-4B22-B7B6-1049D0344E52} 46 | {277D3533-13CC-462E-9BA0-74BAF2658D07} = {CFAC3A63-E145-473E-9AA0-99EA17EC757D} 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IdentityServer4.Contrib.MongoDB 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/v3sy5bjmlfdycjqg?svg=true)](https://ci.appveyor.com/project/diogodamiani/identityserver4-mongodb) 4 | [![NuGet](https://img.shields.io/badge/NuGet-4.0.0-blue.svg)](https://www.nuget.org/packages/IdentityServer4.Contrib.MongoDB/) 5 | 6 | MongoDB persistence layer for IdentityServer4 based on the Official [EntityFramework](https://github.com/IdentityServer/IdentityServer4.EntityFramework) persistence layer. 7 | -------------------------------------------------------------------------------- /UPDATE.md: -------------------------------------------------------------------------------- 1 | # Breaking Changes 2 | 3 | ## **v4.0.0** 4 | 5 | As of IdentityServer v4.0.0 API scopes in the API resource model are now stored as strings. So, it will require a data schema update. 6 | 7 | The *Scopes* property of *APIResource* object must change: 8 | 9 | From: 10 | 11 | ``` 12 | Scopes = 13 | { 14 | new Scope() 15 | { 16 | Name = "api2.full_access", 17 | DisplayName = "Full access to API 2" 18 | }, 19 | new Scope 20 | { 21 | Name = "api2.read_only", 22 | DisplayName = "Read only access to API 2" 23 | } 24 | } 25 | ``` 26 | 27 | To: 28 | 29 | ``` 30 | Scopes = 31 | { 32 | "api2.full_access", 33 | "api2.read_only" 34 | } 35 | ``` 36 | 37 | *ApiScope* objects are stored in an exclusive colletion inside *ConfigurationDbContext*. -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | 3 | image: Visual Studio 2019 4 | 5 | install: 6 | - cmd: curl -O https://download.visualstudio.microsoft.com/download/pr/046165a4-10d4-4156-8e65-1d7b2cbd304e/a4c7b01f6bf7199669a45ab6a03803ac/dotnet-sdk-3.1.412-win-x64.exe 7 | - cmd: dotnet-sdk-3.1.412-win-x64.exe /install /quiet /norestart /log install.log 8 | 9 | branches: 10 | only: 11 | - dev 12 | 13 | build_script: 14 | - ps: .\build.ps1 15 | 16 | test: off 17 | 18 | artifacts: 19 | - path: artifacts/packages/*.nupkg 20 | 21 | deploy: 22 | - provider: NuGet 23 | server: https://www.myget.org/F/damiani/ 24 | api_key: 25 | secure: XOm6HAYrck/gljqdDrLWHopqnOs/FwbstHoi61nIEX6SJNUyccAaLJ4hjsyHoSF8 26 | skip_symbols: true 27 | on: 28 | branch: dev 29 | 30 | #---------------------------------# 31 | # Skip builds for doc changes # 32 | #---------------------------------# 33 | skip_commits: 34 | # Regex for matching commit message 35 | message: /docs.*/ 36 | files: 37 | - ./*.md 38 | -------------------------------------------------------------------------------- /build.cake: -------------------------------------------------------------------------------- 1 | var target = Argument("target", "Default"); 2 | var configuration = Argument("configuration", "Release"); 3 | 4 | /////////////////////////////////////////////////////////////////////////////// 5 | // GLOBAL VARIABLES 6 | /////////////////////////////////////////////////////////////////////////////// 7 | var isAppVeyor = AppVeyor.IsRunningOnAppVeyor; 8 | var isWindows = IsRunningOnWindows(); 9 | var netcore = "netcoreapp1.1"; 10 | var netstandard = "netstandard1.5"; 11 | 12 | var libIS4MongoPath = Directory("./src/IdentityServer4.MongoDB"); 13 | var demoHostPath = Directory("./src/Host"); 14 | var buildArtifacts = Directory("./artifacts/packages"); 15 | 16 | /////////////////////////////////////////////////////////////////////////////// 17 | Task("Clean") 18 | .Does(() => 19 | { 20 | CleanDirectories(new DirectoryPath[] { buildArtifacts }); 21 | }); 22 | 23 | /////////////////////////////////////////////////////////////////////////////// 24 | Task("Restore") 25 | .Does(() => 26 | { 27 | var settings = new DotNetCoreRestoreSettings 28 | { 29 | Sources = new [] { "https://api.nuget.org/v3/index.json" } 30 | }; 31 | 32 | var projects = GetFiles("./**/*.csproj"); 33 | 34 | foreach(var project in projects) 35 | { 36 | DotNetCoreRestore(project.GetDirectory().FullPath, settings); 37 | } 38 | }); 39 | 40 | /////////////////////////////////////////////////////////////////////////////// 41 | Task("Build") 42 | .IsDependentOn("Clean") 43 | .IsDependentOn("Restore") 44 | .Does(() => 45 | { 46 | var settings = new DotNetCoreBuildSettings 47 | { 48 | Configuration = configuration 49 | }; 50 | 51 | var projects = GetFiles("./**/*.csproj"); 52 | 53 | // main build (Windows local and Appveyor) 54 | // build for all targets 55 | if (isWindows) 56 | { 57 | foreach(var project in projects) 58 | { 59 | DotNetCoreBuild(project.GetDirectory().FullPath, settings); 60 | } 61 | } 62 | // local mac / travis 63 | // don't build for .net framework 64 | else 65 | { 66 | settings.Framework = netstandard; 67 | DotNetCoreBuild(libIS4MongoPath, settings); 68 | 69 | settings.Framework = netcore; 70 | DotNetCoreBuild(demoHostPath, settings); 71 | } 72 | }); 73 | 74 | /////////////////////////////////////////////////////////////////////////////// 75 | //Task("Test") 76 | // .IsDependentOn("Restore") 77 | // .IsDependentOn("Clean") 78 | // .Does(() => 79 | //{ 80 | // var settings = new DotNetCoreTestSettings 81 | // { 82 | // Configuration = configuration 83 | // }; 84 | // 85 | // if (!isWindows) 86 | // { 87 | // Information("Not running on Windows - skipping tests for full .NET Framework"); 88 | // settings.Framework = netcore; 89 | // } 90 | // 91 | // var projects = GetFiles("./test/**/*.csproj"); 92 | // foreach(var project in projects) 93 | // { 94 | // DotNetCoreTest(project.FullPath, settings); 95 | // } 96 | //}); 97 | 98 | /////////////////////////////////////////////////////////////////////////////// 99 | Task("Pack") 100 | .IsDependentOn("Restore") 101 | .IsDependentOn("Clean") 102 | .Does(() => 103 | { 104 | if (!isWindows) 105 | { 106 | Information("Not running on Windows - skipping pack"); 107 | return; 108 | } 109 | 110 | var settings = new DotNetCorePackSettings 111 | { 112 | Configuration = configuration, 113 | OutputDirectory = buildArtifacts, 114 | }; 115 | 116 | // add build suffix for CI builds 117 | if(isAppVeyor) 118 | { 119 | settings.VersionSuffix = "build" + AppVeyor.Environment.Build.Number.ToString().PadLeft(5,'0'); 120 | } 121 | 122 | DotNetCorePack(libIS4MongoPath, settings); 123 | }); 124 | 125 | /////////////////////////////////////////////////////////////////////////////// 126 | Task("Default") 127 | .IsDependentOn("Build") 128 | //.IsDependentOn("Test") 129 | .IsDependentOn("Pack"); 130 | 131 | RunTarget(target); -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | ########################################################################## 2 | # This is the Cake bootstrapper script for PowerShell. 3 | # This file was downloaded from https://github.com/cake-build/resources 4 | # Feel free to change this file to fit your needs. 5 | ########################################################################## 6 | 7 | <# 8 | 9 | .SYNOPSIS 10 | This is a Powershell script to bootstrap a Cake build. 11 | 12 | .DESCRIPTION 13 | This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) 14 | and execute your Cake build script with the parameters you provide. 15 | 16 | .PARAMETER Script 17 | The build script to execute. 18 | .PARAMETER Target 19 | The build script target to run. 20 | .PARAMETER Configuration 21 | The build configuration to use. 22 | .PARAMETER Verbosity 23 | Specifies the amount of information to be displayed. 24 | .PARAMETER Experimental 25 | Tells Cake to use the latest Roslyn release. 26 | .PARAMETER WhatIf 27 | Performs a dry run of the build script. 28 | No tasks will be executed. 29 | .PARAMETER Mono 30 | Tells Cake to use the Mono scripting engine. 31 | .PARAMETER SkipToolPackageRestore 32 | Skips restoring of packages. 33 | .PARAMETER ScriptArgs 34 | Remaining arguments are added here. 35 | 36 | .LINK 37 | http://cakebuild.net 38 | 39 | #> 40 | 41 | [CmdletBinding()] 42 | Param( 43 | [string]$Script = "build.cake", 44 | [string]$Target = "Default", 45 | [ValidateSet("Release", "Debug")] 46 | [string]$Configuration = "Release", 47 | [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] 48 | [string]$Verbosity = "Verbose", 49 | [switch]$Experimental, 50 | [Alias("DryRun","Noop")] 51 | [switch]$WhatIf, 52 | [switch]$Mono, 53 | [switch]$SkipToolPackageRestore, 54 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] 55 | [string[]]$ScriptArgs 56 | ) 57 | 58 | [Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null 59 | function MD5HashFile([string] $filePath) 60 | { 61 | if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) 62 | { 63 | return $null 64 | } 65 | 66 | [System.IO.Stream] $file = $null; 67 | [System.Security.Cryptography.MD5] $md5 = $null; 68 | try 69 | { 70 | $md5 = [System.Security.Cryptography.MD5]::Create() 71 | $file = [System.IO.File]::OpenRead($filePath) 72 | return [System.BitConverter]::ToString($md5.ComputeHash($file)) 73 | } 74 | finally 75 | { 76 | if ($file -ne $null) 77 | { 78 | $file.Dispose() 79 | } 80 | } 81 | } 82 | 83 | Write-Host "Preparing to run build script..." 84 | 85 | if(!$PSScriptRoot){ 86 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 87 | } 88 | 89 | $TOOLS_DIR = Join-Path $PSScriptRoot "tools" 90 | $NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" 91 | $CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" 92 | $NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" 93 | $PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" 94 | $PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" 95 | 96 | # Should we use mono? 97 | $UseMono = ""; 98 | if($Mono.IsPresent) { 99 | Write-Verbose -Message "Using the Mono based scripting engine." 100 | $UseMono = "-mono" 101 | } 102 | 103 | # Should we use the new Roslyn? 104 | $UseExperimental = ""; 105 | if($Experimental.IsPresent -and !($Mono.IsPresent)) { 106 | Write-Verbose -Message "Using experimental version of Roslyn." 107 | $UseExperimental = "-experimental" 108 | } 109 | 110 | # Is this a dry run? 111 | $UseDryRun = ""; 112 | if($WhatIf.IsPresent) { 113 | $UseDryRun = "-dryrun" 114 | } 115 | 116 | # Make sure tools folder exists 117 | if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { 118 | Write-Verbose -Message "Creating tools directory..." 119 | New-Item -Path $TOOLS_DIR -Type directory | out-null 120 | } 121 | 122 | # Make sure that packages.config exist. 123 | if (!(Test-Path $PACKAGES_CONFIG)) { 124 | Write-Verbose -Message "Downloading packages.config..." 125 | try { (New-Object System.Net.WebClient).DownloadFile("http://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch { 126 | Throw "Could not download packages.config." 127 | } 128 | } 129 | 130 | # Try find NuGet.exe in path if not exists 131 | if (!(Test-Path $NUGET_EXE)) { 132 | Write-Verbose -Message "Trying to find nuget.exe in PATH..." 133 | $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_) } 134 | $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 135 | if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { 136 | Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." 137 | $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName 138 | } 139 | } 140 | 141 | # Try download NuGet.exe if not exists 142 | if (!(Test-Path $NUGET_EXE)) { 143 | Write-Verbose -Message "Downloading NuGet.exe..." 144 | try { 145 | (New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE) 146 | } catch { 147 | Throw "Could not download NuGet.exe." 148 | } 149 | } 150 | 151 | # Save nuget.exe path to environment to be available to child processed 152 | $ENV:NUGET_EXE = $NUGET_EXE 153 | 154 | # Restore tools from NuGet? 155 | if(-Not $SkipToolPackageRestore.IsPresent) { 156 | Push-Location 157 | Set-Location $TOOLS_DIR 158 | 159 | # Check for changes in packages.config and remove installed tools if true. 160 | [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) 161 | if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or 162 | ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { 163 | Write-Verbose -Message "Missing or changed package.config hash..." 164 | Remove-Item * -Recurse -Exclude packages.config,nuget.exe 165 | } 166 | 167 | Write-Verbose -Message "Restoring tools from NuGet..." 168 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" 169 | 170 | if ($LASTEXITCODE -ne 0) { 171 | Throw "An error occured while restoring NuGet tools." 172 | } 173 | else 174 | { 175 | $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" 176 | } 177 | Write-Verbose -Message ($NuGetOutput | out-string) 178 | Pop-Location 179 | } 180 | 181 | # Make sure that Cake has been installed. 182 | if (!(Test-Path $CAKE_EXE)) { 183 | Throw "Could not find Cake.exe at $CAKE_EXE" 184 | } 185 | 186 | # Start Cake 187 | Write-Host "Running build script..." 188 | Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs" 189 | exit $LASTEXITCODE -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ########################################################################## 4 | # This is the Cake bootstrapper script for Linux and OS X. 5 | # This file was downloaded from https://github.com/cake-build/resources 6 | # Feel free to change this file to fit your needs. 7 | ########################################################################## 8 | 9 | # Define directories. 10 | SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 11 | TOOLS_DIR=$SCRIPT_DIR/tools 12 | NUGET_EXE=$TOOLS_DIR/nuget.exe 13 | CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe 14 | PACKAGES_CONFIG=$TOOLS_DIR/packages.config 15 | PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum 16 | 17 | # Define md5sum or md5 depending on Linux/OSX 18 | MD5_EXE= 19 | if [[ "$(uname -s)" == "Darwin" ]]; then 20 | MD5_EXE="md5 -r" 21 | else 22 | MD5_EXE="md5sum" 23 | fi 24 | 25 | # Define default arguments. 26 | SCRIPT="build.cake" 27 | TARGET="Default" 28 | CONFIGURATION="Release" 29 | VERBOSITY="verbose" 30 | DRYRUN= 31 | SHOW_VERSION=false 32 | SCRIPT_ARGUMENTS=() 33 | 34 | # Parse arguments. 35 | for i in "$@"; do 36 | case $1 in 37 | -s|--script) SCRIPT="$2"; shift ;; 38 | -t|--target) TARGET="$2"; shift ;; 39 | -c|--configuration) CONFIGURATION="$2"; shift ;; 40 | -v|--verbosity) VERBOSITY="$2"; shift ;; 41 | -d|--dryrun) DRYRUN="-dryrun" ;; 42 | --version) SHOW_VERSION=true ;; 43 | --) shift; SCRIPT_ARGUMENTS+=("$@"); break ;; 44 | *) SCRIPT_ARGUMENTS+=("$1") ;; 45 | esac 46 | shift 47 | done 48 | 49 | # Make sure the tools folder exist. 50 | if [ ! -d "$TOOLS_DIR" ]; then 51 | mkdir "$TOOLS_DIR" 52 | fi 53 | 54 | # Make sure that packages.config exist. 55 | if [ ! -f "$TOOLS_DIR/packages.config" ]; then 56 | echo "Downloading packages.config..." 57 | curl -Lsfo "$TOOLS_DIR/packages.config" http://cakebuild.net/download/bootstrapper/packages 58 | if [ $? -ne 0 ]; then 59 | echo "An error occured while downloading packages.config." 60 | exit 1 61 | fi 62 | fi 63 | 64 | # Download NuGet if it does not exist. 65 | if [ ! -f "$NUGET_EXE" ]; then 66 | echo "Downloading NuGet..." 67 | curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe 68 | if [ $? -ne 0 ]; then 69 | echo "An error occured while downloading nuget.exe." 70 | exit 1 71 | fi 72 | fi 73 | 74 | # Restore tools from NuGet. 75 | pushd "$TOOLS_DIR" >/dev/null 76 | if [ ! -f $PACKAGES_CONFIG_MD5 ] || [ "$( cat $PACKAGES_CONFIG_MD5 | sed 's/\r$//' )" != "$( $MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' )" ]; then 77 | find . -type d ! -name . | xargs rm -rf 78 | fi 79 | 80 | mono "$NUGET_EXE" install -ExcludeVersion 81 | if [ $? -ne 0 ]; then 82 | echo "Could not restore NuGet packages." 83 | exit 1 84 | fi 85 | 86 | $MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' >| $PACKAGES_CONFIG_MD5 87 | 88 | popd >/dev/null 89 | 90 | # Make sure that Cake has been installed. 91 | if [ ! -f "$CAKE_EXE" ]; then 92 | echo "Could not find Cake.exe at '$CAKE_EXE'." 93 | exit 1 94 | fi 95 | 96 | # Start Cake 97 | if $SHOW_VERSION; then 98 | exec mono "$CAKE_EXE" -version 99 | else 100 | exec mono "$CAKE_EXE" $SCRIPT -verbosity=$VERBOSITY -configuration=$CONFIGURATION -target=$TARGET $DRYRUN "${SCRIPT_ARGUMENTS[@]}" 101 | fi -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | {"projects":["src"]} -------------------------------------------------------------------------------- /src/Host/Configuration/Clients.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4; 6 | using IdentityServer4.Models; 7 | using System.Collections.Generic; 8 | 9 | namespace Host.Configuration 10 | { 11 | public class Clients 12 | { 13 | public static IEnumerable Get() 14 | { 15 | return new List 16 | { 17 | /////////////////////////////////////////// 18 | // Console Client Credentials Flow Sample 19 | ////////////////////////////////////////// 20 | new Client 21 | { 22 | ClientId = "client", 23 | ClientSecrets = 24 | { 25 | new Secret("secret".Sha256()) 26 | }, 27 | 28 | AllowedGrantTypes = GrantTypes.ClientCredentials, 29 | AllowedScopes = { "api1", "api2.read_only" } 30 | }, 31 | 32 | /////////////////////////////////////////// 33 | // Console Client Credentials Flow with client JWT assertion 34 | ////////////////////////////////////////// 35 | new Client 36 | { 37 | ClientId = "client.jwt", 38 | ClientSecrets = 39 | { 40 | new Secret 41 | { 42 | Type = IdentityServerConstants.SecretTypes.X509CertificateBase64, 43 | Value = "MIIDATCCAe2gAwIBAgIQoHUYAquk9rBJcq8W+F0FAzAJBgUrDgMCHQUAMBIxEDAOBgNVBAMTB0RldlJvb3QwHhcNMTAwMTIwMjMwMDAwWhcNMjAwMTIwMjMwMDAwWjARMQ8wDQYDVQQDEwZDbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDSaY4x1eXqjHF1iXQcF3pbFrIbmNw19w/IdOQxbavmuPbhY7jX0IORu/GQiHjmhqWt8F4G7KGLhXLC1j7rXdDmxXRyVJBZBTEaSYukuX7zGeUXscdpgODLQVay/0hUGz54aDZPAhtBHaYbog+yH10sCXgV1Mxtzx3dGelA6pPwiAmXwFxjJ1HGsS/hdbt+vgXhdlzud3ZSfyI/TJAnFeKxsmbJUyqMfoBl1zFKG4MOvgHhBjekp+r8gYNGknMYu9JDFr1ue0wylaw9UwG8ZXAkYmYbn2wN/CpJl3gJgX42/9g87uLvtVAmz5L+rZQTlS1ibv54ScR2lcRpGQiQav/LAgMBAAGjXDBaMBMGA1UdJQQMMAoGCCsGAQUFBwMCMEMGA1UdAQQ8MDqAENIWANpX5DZ3bX3WvoDfy0GhFDASMRAwDgYDVQQDEwdEZXZSb290ghAsWTt7E82DjU1E1p427Qj2MAkGBSsOAwIdBQADggEBADLje0qbqGVPaZHINLn+WSM2czZk0b5NG80btp7arjgDYoWBIe2TSOkkApTRhLPfmZTsaiI3Ro/64q+Dk3z3Kt7w+grHqu5nYhsn7xQFAQUf3y2KcJnRdIEk0jrLM4vgIzYdXsoC6YO+9QnlkNqcN36Y8IpSVSTda6gRKvGXiAhu42e2Qey/WNMFOL+YzMXGt/nDHL/qRKsuXBOarIb++43DV3YnxGTx22llhOnPpuZ9/gnNY7KLjODaiEciKhaKqt/b57mTEz4jTF4kIg6BP03MUfDXeVlM1Qf1jB43G2QQ19n5lUiqTpmQkcfLfyci2uBZ8BkOhXr3Vk9HIk/xBXQ=" 44 | } 45 | }, 46 | 47 | AllowedGrantTypes = GrantTypes.ClientCredentials, 48 | AllowedScopes = { "api1", "api2.read_only" } 49 | }, 50 | 51 | /////////////////////////////////////////// 52 | // Custom Grant Sample 53 | ////////////////////////////////////////// 54 | new Client 55 | { 56 | ClientId = "client.custom", 57 | ClientSecrets = 58 | { 59 | new Secret("secret".Sha256()) 60 | }, 61 | 62 | AllowedGrantTypes = { "custom", "custom.nosubject" }, 63 | AllowedScopes = { "api1", "api2.read_only" } 64 | }, 65 | 66 | /////////////////////////////////////////// 67 | // Console Resource Owner Flow Sample 68 | ////////////////////////////////////////// 69 | new Client 70 | { 71 | ClientId = "roclient", 72 | ClientSecrets = 73 | { 74 | new Secret("secret".Sha256()) 75 | }, 76 | 77 | AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, 78 | 79 | AllowOfflineAccess = true, 80 | AllowedScopes = 81 | { 82 | IdentityServerConstants.StandardScopes.OpenId, 83 | "custom.profile", 84 | "api1", "api2.read_only" 85 | } 86 | }, 87 | 88 | /////////////////////////////////////////// 89 | // Console Public Resource Owner Flow Sample 90 | ////////////////////////////////////////// 91 | new Client 92 | { 93 | ClientId = "roclient.public", 94 | RequireClientSecret = false, 95 | 96 | AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, 97 | 98 | AllowOfflineAccess = true, 99 | AllowedScopes = 100 | { 101 | IdentityServerConstants.StandardScopes.OpenId, 102 | IdentityServerConstants.StandardScopes.Email, 103 | "api1", "api2.read_only" 104 | } 105 | }, 106 | 107 | /////////////////////////////////////////// 108 | // Console Hybrid with PKCE Sample 109 | ////////////////////////////////////////// 110 | new Client 111 | { 112 | ClientId = "console.hybrid.pkce", 113 | ClientName = "Console Hybrid with PKCE Sample", 114 | RequireClientSecret = false, 115 | 116 | AllowedGrantTypes = GrantTypes.Hybrid, 117 | RequirePkce = true, 118 | 119 | RedirectUris = { "http://127.0.0.1" }, 120 | 121 | AllowOfflineAccess = true, 122 | 123 | AllowedScopes = 124 | { 125 | IdentityServerConstants.StandardScopes.OpenId, 126 | IdentityServerConstants.StandardScopes.Profile, 127 | IdentityServerConstants.StandardScopes.Email, 128 | "api1", "api2.read_only" 129 | } 130 | }, 131 | 132 | /////////////////////////////////////////// 133 | // Introspection Client Sample 134 | ////////////////////////////////////////// 135 | new Client 136 | { 137 | ClientId = "roclient.reference", 138 | ClientSecrets = 139 | { 140 | new Secret("secret".Sha256()) 141 | }, 142 | 143 | AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, 144 | AllowedScopes = { "api1", "api2.read_only" }, 145 | 146 | AccessTokenType = AccessTokenType.Reference 147 | }, 148 | 149 | /////////////////////////////////////////// 150 | // MVC Implicit Flow Samples 151 | ////////////////////////////////////////// 152 | new Client 153 | { 154 | ClientId = "mvc.implicit", 155 | ClientName = "MVC Implicit", 156 | ClientUri = "http://identityserver.io", 157 | 158 | AllowedGrantTypes = GrantTypes.Implicit, 159 | AllowAccessTokensViaBrowser = true, 160 | 161 | RedirectUris = { "http://localhost:44077/signin-oidc" }, 162 | FrontChannelLogoutUri = "http://localhost:44077/signout-oidc", 163 | PostLogoutRedirectUris = { "http://localhost:44077/signout-callback-oidc" }, 164 | 165 | AllowedScopes = 166 | { 167 | IdentityServerConstants.StandardScopes.OpenId, 168 | IdentityServerConstants.StandardScopes.Profile, 169 | IdentityServerConstants.StandardScopes.Email, 170 | "api1", "api2.read_only" 171 | } 172 | }, 173 | 174 | /////////////////////////////////////////// 175 | // MVC Manual Implicit Flow Sample 176 | ////////////////////////////////////////// 177 | new Client 178 | { 179 | ClientId = "mvc.manual", 180 | ClientName = "MVC Manual", 181 | ClientUri = "http://identityserver.io", 182 | 183 | AllowedGrantTypes = GrantTypes.Implicit, 184 | 185 | RedirectUris = { "http://localhost:44078/home/callback" }, 186 | FrontChannelLogoutUri = "http://localhost:44078/signout-oidc", 187 | PostLogoutRedirectUris = { "http://localhost:44078/" }, 188 | 189 | AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId } 190 | }, 191 | 192 | /////////////////////////////////////////// 193 | // MVC Hybrid Flow Samples 194 | ////////////////////////////////////////// 195 | new Client 196 | { 197 | ClientId = "mvc.hybrid", 198 | ClientName = "MVC Hybrid", 199 | ClientUri = "http://identityserver.io", 200 | //LogoUri = "https://pbs.twimg.com/profile_images/1612989113/Ki-hanja_400x400.png", 201 | 202 | ClientSecrets = 203 | { 204 | new Secret("secret".Sha256()) 205 | }, 206 | 207 | AllowedGrantTypes = GrantTypes.Hybrid, 208 | AllowAccessTokensViaBrowser = false, 209 | 210 | RedirectUris = { "http://localhost:21402/signin-oidc" }, 211 | FrontChannelLogoutUri = "http://localhost:21402/signout-oidc", 212 | PostLogoutRedirectUris = { "http://localhost:21402/signout-callback-oidc" }, 213 | 214 | AllowOfflineAccess = true, 215 | 216 | AllowedScopes = 217 | { 218 | IdentityServerConstants.StandardScopes.OpenId, 219 | IdentityServerConstants.StandardScopes.Profile, 220 | IdentityServerConstants.StandardScopes.Email, 221 | "api1", "api2.read_only" 222 | } 223 | }, 224 | 225 | /////////////////////////////////////////// 226 | // JS OAuth 2.0 Sample 227 | ////////////////////////////////////////// 228 | new Client 229 | { 230 | ClientId = "js_oauth", 231 | ClientName = "JavaScript OAuth 2.0 Client", 232 | ClientUri = "http://identityserver.io", 233 | //LogoUri = "https://pbs.twimg.com/profile_images/1612989113/Ki-hanja_400x400.png", 234 | 235 | AllowedGrantTypes = GrantTypes.Implicit, 236 | AllowAccessTokensViaBrowser = true, 237 | 238 | RedirectUris = { "http://localhost:28895/index.html" }, 239 | AllowedScopes = { "api1", "api2.read_only" } 240 | }, 241 | 242 | /////////////////////////////////////////// 243 | // JS OIDC Sample 244 | ////////////////////////////////////////// 245 | new Client 246 | { 247 | ClientId = "js_oidc", 248 | ClientName = "JavaScript OIDC Client", 249 | ClientUri = "http://identityserver.io", 250 | //LogoUri = "https://pbs.twimg.com/profile_images/1612989113/Ki-hanja_400x400.png", 251 | 252 | AllowedGrantTypes = GrantTypes.Implicit, 253 | AllowAccessTokensViaBrowser = true, 254 | RequireClientSecret = false, 255 | AccessTokenType = AccessTokenType.Jwt, 256 | 257 | RedirectUris = 258 | { 259 | "http://localhost:7017/index.html", 260 | "http://localhost:7017/callback.html", 261 | "http://localhost:7017/silent.html", 262 | "http://localhost:7017/popup.html" 263 | }, 264 | 265 | PostLogoutRedirectUris = { "http://localhost:7017/index.html" }, 266 | AllowedCorsOrigins = { "http://localhost:7017" }, 267 | 268 | AllowedScopes = 269 | { 270 | IdentityServerConstants.StandardScopes.OpenId, 271 | IdentityServerConstants.StandardScopes.Profile, 272 | IdentityServerConstants.StandardScopes.Email, 273 | "api1", "api2.read_only", "api2.full_access" 274 | } 275 | } 276 | }; 277 | } 278 | } 279 | } -------------------------------------------------------------------------------- /src/Host/Configuration/Resources.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityModel; 6 | using IdentityServer4.Models; 7 | using System.Collections.Generic; 8 | 9 | namespace Host.Configuration 10 | { 11 | public class Resources 12 | { 13 | public static IEnumerable GetIdentityResources() 14 | { 15 | return new[] 16 | { 17 | // some standard scopes from the OIDC spec 18 | new IdentityResources.OpenId(), 19 | new IdentityResources.Profile(), 20 | new IdentityResources.Email(), 21 | 22 | // custom identity resource with some consolidated claims 23 | new IdentityResource("custom.profile", new[] { JwtClaimTypes.Name, JwtClaimTypes.Email, "location" }) 24 | }; 25 | } 26 | 27 | public static IEnumerable GetApiResources() 28 | { 29 | return new[] 30 | { 31 | // simple version with ctor 32 | new ApiResource("api1", "Some API 1") 33 | { 34 | // this is needed for introspection when using reference tokens 35 | ApiSecrets = { new Secret("secret".Sha256()) } 36 | }, 37 | 38 | // expanded version if more control is needed 39 | new ApiResource 40 | { 41 | Name = "api2", 42 | 43 | ApiSecrets = 44 | { 45 | new Secret("secret".Sha256()) 46 | }, 47 | 48 | UserClaims = 49 | { 50 | JwtClaimTypes.Name, 51 | JwtClaimTypes.Email 52 | }, 53 | 54 | Scopes = 55 | { 56 | "api2.full_access", 57 | "api2.read_only" 58 | } 59 | } 60 | }; 61 | } 62 | 63 | public static IEnumerable GetApiScopes() 64 | { 65 | return new[] 66 | { 67 | new ApiScope() 68 | { 69 | Name = "api2.full_access", 70 | DisplayName = "Full access to API 2", 71 | }, 72 | new ApiScope 73 | { 74 | Name = "api2.read_only", 75 | DisplayName = "Read only access to API 2" 76 | } 77 | }; 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /src/Host/Extensions/ExtensionGrantValidator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.Models; 6 | using IdentityServer4.Validation; 7 | using System.Threading.Tasks; 8 | 9 | namespace Host.Extensions 10 | { 11 | public class ExtensionGrantValidator : IExtensionGrantValidator 12 | { 13 | public Task ValidateAsync(ExtensionGrantValidationContext context) 14 | { 15 | var credential = context.Request.Raw.Get("custom_credential"); 16 | 17 | if (credential != null) 18 | { 19 | context.Result = new GrantValidationResult(subject: "818727", authenticationMethod: "custom"); 20 | } 21 | else 22 | { 23 | // custom error message 24 | context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid custom credential"); 25 | } 26 | 27 | return Task.CompletedTask; 28 | } 29 | 30 | public string GrantType 31 | { 32 | get { return "custom"; } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/Host/Extensions/NoSubjectExtensionGrantValidator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.Models; 6 | using IdentityServer4.Validation; 7 | using System.Threading.Tasks; 8 | 9 | namespace Host.Extensions 10 | { 11 | public class NoSubjectExtensionGrantValidator : IExtensionGrantValidator 12 | { 13 | public Task ValidateAsync(ExtensionGrantValidationContext context) 14 | { 15 | var credential = context.Request.Raw.Get("custom_credential"); 16 | 17 | if (credential != null) 18 | { 19 | context.Result = new GrantValidationResult(); 20 | } 21 | else 22 | { 23 | // custom error message 24 | context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid custom credential"); 25 | } 26 | 27 | return Task.CompletedTask; 28 | } 29 | 30 | public string GrantType 31 | { 32 | get { return "custom.nosubject"; } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/Host/Host.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | true 6 | Host 7 | Exe 8 | Host 9 | win10-x64;win7-x64 10 | 11 | 12 | 13 | 14 | PreserveNewest 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/Host/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using Microsoft.AspNetCore.Hosting; 6 | using System; 7 | using Microsoft.Extensions.Logging; 8 | using Serilog; 9 | using Serilog.Sinks.SystemConsole.Themes; 10 | using Microsoft.AspNetCore; 11 | using Serilog.Events; 12 | 13 | namespace Host 14 | { 15 | public class Program 16 | { 17 | public static void Main(string[] args) 18 | { 19 | Console.Title = "IdentityServer4"; 20 | 21 | Log.Logger = new LoggerConfiguration() 22 | .MinimumLevel.Debug() 23 | .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) 24 | .MinimumLevel.Override("System", LogEventLevel.Warning) 25 | .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) 26 | .Enrich.FromLogContext() 27 | .WriteTo.File(@"identityserver4_log.txt") 28 | .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Literate) 29 | .CreateLogger(); 30 | 31 | BuildWebHost(args).Run(); 32 | } 33 | 34 | public static IWebHost BuildWebHost(string[] args) 35 | { 36 | return WebHost.CreateDefaultBuilder(args) 37 | .UseStartup() 38 | .ConfigureLogging(builder => 39 | { 40 | builder.ClearProviders(); 41 | builder.AddSerilog(); 42 | }) 43 | .Build(); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/Host/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": true, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:5000", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "Host": { 19 | "commandName": "Project", 20 | "environmentVariables": { 21 | "ASPNETCORE_ENVIRONMENT": "Development" 22 | }, 23 | "applicationUrl": "http://localhost:5000" 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/Host/Quickstart/Account/AccountOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System; 6 | 7 | namespace IdentityServer4.Quickstart.UI 8 | { 9 | public class AccountOptions 10 | { 11 | public static bool AllowLocalLogin = true; 12 | public static bool AllowRememberLogin = true; 13 | public static TimeSpan RememberMeLoginDuration = TimeSpan.FromDays(30); 14 | 15 | public static bool ShowLogoutPrompt = true; 16 | public static bool AutomaticRedirectAfterSignOut = false; 17 | 18 | // to enable windows authentication, the host (IIS or IIS Express) also must have 19 | // windows auth enabled. 20 | public static bool WindowsAuthenticationEnabled = true; 21 | public static bool IncludeWindowsGroups = false; 22 | // specify the Windows authentication scheme and display name 23 | public static readonly string WindowsAuthenticationSchemeName = "Windows"; 24 | 25 | public static string InvalidCredentialsErrorMessage = "Invalid username or password"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Host/Quickstart/Account/AccountService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityModel; 6 | using IdentityServer4.Extensions; 7 | using IdentityServer4.Services; 8 | using IdentityServer4.Stores; 9 | using Microsoft.AspNetCore.Authentication; 10 | using Microsoft.AspNetCore.Http; 11 | using System.Linq; 12 | using System.Threading.Tasks; 13 | 14 | namespace IdentityServer4.Quickstart.UI 15 | { 16 | public class AccountService 17 | { 18 | private readonly IClientStore _clientStore; 19 | private readonly IIdentityServerInteractionService _interaction; 20 | private readonly IHttpContextAccessor _httpContextAccessor; 21 | private readonly IAuthenticationSchemeProvider _schemeProvider; 22 | 23 | public AccountService( 24 | IIdentityServerInteractionService interaction, 25 | IHttpContextAccessor httpContextAccessor, 26 | IAuthenticationSchemeProvider schemeProvider, 27 | IClientStore clientStore) 28 | { 29 | _interaction = interaction; 30 | _httpContextAccessor = httpContextAccessor; 31 | _schemeProvider = schemeProvider; 32 | _clientStore = clientStore; 33 | } 34 | 35 | public async Task BuildLoginViewModelAsync(string returnUrl) 36 | { 37 | var context = await _interaction.GetAuthorizationContextAsync(returnUrl); 38 | if (context?.IdP != null) 39 | { 40 | // this is meant to short circuit the UI and only trigger the one external IdP 41 | return new LoginViewModel 42 | { 43 | EnableLocalLogin = false, 44 | ReturnUrl = returnUrl, 45 | Username = context?.LoginHint, 46 | ExternalProviders = new ExternalProvider[] {new ExternalProvider { AuthenticationScheme = context.IdP } } 47 | }; 48 | } 49 | 50 | var schemes = await _schemeProvider.GetAllSchemesAsync(); 51 | 52 | var providers = schemes 53 | .Where(x => x.DisplayName != null) 54 | .Select(x => new ExternalProvider 55 | { 56 | DisplayName = x.DisplayName, 57 | AuthenticationScheme = x.Name 58 | }).ToList(); 59 | 60 | var allowLocal = true; 61 | if (context?.Client.ClientId != null) 62 | { 63 | var client = await _clientStore.FindEnabledClientByIdAsync(context.Client.ClientId); 64 | if (client != null) 65 | { 66 | allowLocal = client.EnableLocalLogin; 67 | 68 | if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any()) 69 | { 70 | providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList(); 71 | } 72 | } 73 | } 74 | 75 | return new LoginViewModel 76 | { 77 | AllowRememberLogin = AccountOptions.AllowRememberLogin, 78 | EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin, 79 | ReturnUrl = returnUrl, 80 | Username = context?.LoginHint, 81 | ExternalProviders = providers.ToArray() 82 | }; 83 | } 84 | 85 | public async Task BuildLoginViewModelAsync(LoginInputModel model) 86 | { 87 | var vm = await BuildLoginViewModelAsync(model.ReturnUrl); 88 | vm.Username = model.Username; 89 | vm.RememberLogin = model.RememberLogin; 90 | return vm; 91 | } 92 | 93 | public async Task BuildLogoutViewModelAsync(string logoutId) 94 | { 95 | var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt }; 96 | 97 | var user = _httpContextAccessor.HttpContext.User; 98 | if (user?.Identity.IsAuthenticated != true) 99 | { 100 | // if the user is not authenticated, then just show logged out page 101 | vm.ShowLogoutPrompt = false; 102 | return vm; 103 | } 104 | 105 | var context = await _interaction.GetLogoutContextAsync(logoutId); 106 | if (context?.ShowSignoutPrompt == false) 107 | { 108 | // it's safe to automatically sign-out 109 | vm.ShowLogoutPrompt = false; 110 | return vm; 111 | } 112 | 113 | // show the logout prompt. this prevents attacks where the user 114 | // is automatically signed out by another malicious web page. 115 | return vm; 116 | } 117 | 118 | public async Task BuildLoggedOutViewModelAsync(string logoutId) 119 | { 120 | // get context information (client name, post logout redirect URI and iframe for federated signout) 121 | var logout = await _interaction.GetLogoutContextAsync(logoutId); 122 | 123 | var vm = new LoggedOutViewModel 124 | { 125 | AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut, 126 | PostLogoutRedirectUri = logout?.PostLogoutRedirectUri, 127 | ClientName = logout?.ClientId, 128 | SignOutIframeUrl = logout?.SignOutIFrameUrl, 129 | LogoutId = logoutId 130 | }; 131 | 132 | var user = _httpContextAccessor.HttpContext.User; 133 | if (user?.Identity.IsAuthenticated == true) 134 | { 135 | var idp = user.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; 136 | if (idp != null && idp != IdentityServer4.IdentityServerConstants.LocalIdentityProvider) 137 | { 138 | var providerSupportsSignout = await _httpContextAccessor.HttpContext.GetSchemeSupportsSignOutAsync(idp); 139 | if (providerSupportsSignout) 140 | { 141 | if (vm.LogoutId == null) 142 | { 143 | // if there's no current logout context, we need to create one 144 | // this captures necessary info from the current logged in user 145 | // before we signout and redirect away to the external IdP for signout 146 | vm.LogoutId = await _interaction.CreateLogoutContextAsync(); 147 | } 148 | 149 | vm.ExternalAuthenticationScheme = idp; 150 | } 151 | } 152 | } 153 | 154 | return vm; 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/Host/Quickstart/Account/ExternalProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.Quickstart.UI 6 | { 7 | public class ExternalProvider 8 | { 9 | public string DisplayName { get; set; } 10 | public string AuthenticationScheme { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Host/Quickstart/Account/LoggedOutViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.Quickstart.UI 6 | { 7 | public class LoggedOutViewModel 8 | { 9 | public string PostLogoutRedirectUri { get; set; } 10 | public string ClientName { get; set; } 11 | public string SignOutIframeUrl { get; set; } 12 | 13 | public bool AutomaticRedirectAfterSignOut { get; set; } 14 | 15 | public string LogoutId { get; set; } 16 | public bool TriggerExternalSignout => ExternalAuthenticationScheme != null; 17 | public string ExternalAuthenticationScheme { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /src/Host/Quickstart/Account/LoginInputModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System.ComponentModel.DataAnnotations; 6 | 7 | namespace IdentityServer4.Quickstart.UI 8 | { 9 | public class LoginInputModel 10 | { 11 | [Required] 12 | public string Username { get; set; } 13 | [Required] 14 | public string Password { get; set; } 15 | public bool RememberLogin { get; set; } 16 | public string ReturnUrl { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Host/Quickstart/Account/LoginViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | 9 | namespace IdentityServer4.Quickstart.UI 10 | { 11 | public class LoginViewModel : LoginInputModel 12 | { 13 | public bool AllowRememberLogin { get; set; } 14 | public bool EnableLocalLogin { get; set; } 15 | 16 | public IEnumerable ExternalProviders { get; set; } 17 | public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !String.IsNullOrWhiteSpace(x.DisplayName)); 18 | 19 | public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1; 20 | public string ExternalLoginScheme => ExternalProviders?.SingleOrDefault()?.AuthenticationScheme; 21 | } 22 | } -------------------------------------------------------------------------------- /src/Host/Quickstart/Account/LogoutInputModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.Quickstart.UI 6 | { 7 | public class LogoutInputModel 8 | { 9 | public string LogoutId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Host/Quickstart/Account/LogoutViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.Quickstart.UI 6 | { 7 | public class LogoutViewModel : LogoutInputModel 8 | { 9 | public bool ShowLogoutPrompt { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Host/Quickstart/Consent/ConsentController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.Services; 6 | using IdentityServer4.Stores; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.Extensions.Logging; 9 | using System.Threading.Tasks; 10 | 11 | namespace IdentityServer4.Quickstart.UI 12 | { 13 | /// 14 | /// This controller processes the consent UI 15 | /// 16 | [SecurityHeaders] 17 | public class ConsentController : Controller 18 | { 19 | private readonly ConsentService _consent; 20 | 21 | public ConsentController( 22 | IIdentityServerInteractionService interaction, 23 | IClientStore clientStore, 24 | IResourceStore resourceStore, 25 | ILogger logger) 26 | { 27 | _consent = new ConsentService(interaction, clientStore, resourceStore, logger); 28 | } 29 | 30 | /// 31 | /// Shows the consent screen 32 | /// 33 | /// 34 | /// 35 | [HttpGet] 36 | public async Task Index(string returnUrl) 37 | { 38 | var vm = await _consent.BuildViewModelAsync(returnUrl); 39 | if (vm != null) 40 | { 41 | return View("Index", vm); 42 | } 43 | 44 | return View("Error"); 45 | } 46 | 47 | /// 48 | /// Handles the consent screen postback 49 | /// 50 | [HttpPost] 51 | [ValidateAntiForgeryToken] 52 | public async Task Index(ConsentInputModel model) 53 | { 54 | var result = await _consent.ProcessConsent(model); 55 | 56 | if (result.IsRedirect) 57 | { 58 | return Redirect(result.RedirectUri); 59 | } 60 | 61 | if (result.HasValidationError) 62 | { 63 | ModelState.AddModelError("", result.ValidationError); 64 | } 65 | 66 | if (result.ShowView) 67 | { 68 | return View("Index", result.ViewModel); 69 | } 70 | 71 | return View("Error"); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/Host/Quickstart/Consent/ConsentInputModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System.Collections.Generic; 6 | 7 | namespace IdentityServer4.Quickstart.UI 8 | { 9 | public class ConsentInputModel 10 | { 11 | public string Button { get; set; } 12 | public IEnumerable ScopesConsented { get; set; } 13 | public bool RememberConsent { get; set; } 14 | public string ReturnUrl { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Host/Quickstart/Consent/ConsentOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.Quickstart.UI 6 | { 7 | public class ConsentOptions 8 | { 9 | public static bool EnableOfflineAccess = true; 10 | public static string OfflineAccessDisplayName = "Offline Access"; 11 | public static string OfflineAccessDescription = "Access to your applications and resources, even when you are offline"; 12 | 13 | public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission"; 14 | public static readonly string InvalidSelectionErrorMessage = "Invalid selection"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Host/Quickstart/Consent/ConsentService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.Models; 6 | using IdentityServer4.Services; 7 | using IdentityServer4.Stores; 8 | using Microsoft.Extensions.Logging; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | 12 | namespace IdentityServer4.Quickstart.UI 13 | { 14 | public class ConsentService 15 | { 16 | private readonly IClientStore _clientStore; 17 | private readonly IResourceStore _resourceStore; 18 | private readonly IIdentityServerInteractionService _interaction; 19 | private readonly ILogger _logger; 20 | 21 | public ConsentService( 22 | IIdentityServerInteractionService interaction, 23 | IClientStore clientStore, 24 | IResourceStore resourceStore, 25 | ILogger logger) 26 | { 27 | _interaction = interaction; 28 | _clientStore = clientStore; 29 | _resourceStore = resourceStore; 30 | _logger = logger; 31 | } 32 | 33 | public async Task ProcessConsent(ConsentInputModel model) 34 | { 35 | var result = new ProcessConsentResult(); 36 | 37 | ConsentResponse grantedConsent = null; 38 | 39 | // user clicked 'no' - send back the standard 'access_denied' response 40 | if (model.Button == "no") 41 | { 42 | grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied }; 43 | } 44 | // user clicked 'yes' - validate the data 45 | else if (model.Button == "yes" && model != null) 46 | { 47 | // if the user consented to some scope, build the response model 48 | if (model.ScopesConsented != null && model.ScopesConsented.Any()) 49 | { 50 | var scopes = model.ScopesConsented; 51 | if (ConsentOptions.EnableOfflineAccess == false) 52 | { 53 | scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess); 54 | } 55 | 56 | grantedConsent = new ConsentResponse 57 | { 58 | RememberConsent = model.RememberConsent, 59 | ScopesValuesConsented = scopes.ToArray() 60 | }; 61 | } 62 | else 63 | { 64 | result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; 65 | } 66 | } 67 | else 68 | { 69 | result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; 70 | } 71 | 72 | if (grantedConsent != null) 73 | { 74 | // validate return url is still valid 75 | var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); 76 | if (request == null) return result; 77 | 78 | // communicate outcome of consent back to identityserver 79 | await _interaction.GrantConsentAsync(request, grantedConsent); 80 | 81 | // indicate that's it ok to redirect back to authorization endpoint 82 | result.RedirectUri = model.ReturnUrl; 83 | } 84 | else 85 | { 86 | // we need to redisplay the consent UI 87 | result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model); 88 | } 89 | 90 | return result; 91 | } 92 | 93 | public async Task BuildViewModelAsync(string returnUrl, ConsentInputModel model = null) 94 | { 95 | var request = await _interaction.GetAuthorizationContextAsync(returnUrl); 96 | if (request != null) 97 | { 98 | var client = await _clientStore.FindEnabledClientByIdAsync(request.Client.ClientId); 99 | if (client != null) 100 | { 101 | var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ValidatedResources.Resources.ToScopeNames()); 102 | if (resources != null && (resources.IdentityResources.Any() || resources.ApiResources.Any())) 103 | { 104 | return CreateConsentViewModel(model, returnUrl, request, client, resources); 105 | } 106 | else 107 | { 108 | _logger.LogError("No scopes matching: {0}", request.ValidatedResources.Resources.ToScopeNames().Aggregate((x, y) => x + ", " + y)); 109 | } 110 | } 111 | else 112 | { 113 | _logger.LogError("Invalid client id: {0}", request.Client.ClientId); 114 | } 115 | } 116 | else 117 | { 118 | _logger.LogError("No consent request matching request: {0}", returnUrl); 119 | } 120 | 121 | return null; 122 | } 123 | 124 | private ConsentViewModel CreateConsentViewModel( 125 | ConsentInputModel model, string returnUrl, 126 | AuthorizationRequest request, 127 | Client client, Resources resources) 128 | { 129 | var vm = new ConsentViewModel(); 130 | vm.RememberConsent = model?.RememberConsent ?? true; 131 | vm.ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(); 132 | 133 | vm.ReturnUrl = returnUrl; 134 | 135 | vm.ClientName = client.ClientName ?? client.ClientId; 136 | vm.ClientUrl = client.ClientUri; 137 | vm.ClientLogoUrl = client.LogoUri; 138 | vm.AllowRememberConsent = client.AllowRememberConsent; 139 | 140 | vm.IdentityScopes = resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); 141 | vm.ResourceScopes = resources.ApiScopes.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); 142 | if (ConsentOptions.EnableOfflineAccess && resources.OfflineAccess) 143 | { 144 | vm.ResourceScopes = vm.ResourceScopes.Union(new ScopeViewModel[] { 145 | GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null) 146 | }); 147 | } 148 | 149 | return vm; 150 | } 151 | 152 | public ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) 153 | { 154 | return new ScopeViewModel 155 | { 156 | Name = identity.Name, 157 | DisplayName = identity.DisplayName, 158 | Description = identity.Description, 159 | Emphasize = identity.Emphasize, 160 | Required = identity.Required, 161 | Checked = check || identity.Required 162 | }; 163 | } 164 | 165 | public ScopeViewModel CreateScopeViewModel(ApiScope scope, bool check) 166 | { 167 | return new ScopeViewModel 168 | { 169 | Name = scope.Name, 170 | DisplayName = scope.DisplayName, 171 | Description = scope.Description, 172 | Emphasize = scope.Emphasize, 173 | Required = scope.Required, 174 | Checked = check || scope.Required 175 | }; 176 | } 177 | 178 | private ScopeViewModel GetOfflineAccessScope(bool check) 179 | { 180 | return new ScopeViewModel 181 | { 182 | Name = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, 183 | DisplayName = ConsentOptions.OfflineAccessDisplayName, 184 | Description = ConsentOptions.OfflineAccessDescription, 185 | Emphasize = true, 186 | Checked = check 187 | }; 188 | } 189 | } 190 | } 191 | 192 | -------------------------------------------------------------------------------- /src/Host/Quickstart/Consent/ConsentViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System.Collections.Generic; 6 | 7 | namespace IdentityServer4.Quickstart.UI 8 | { 9 | public class ConsentViewModel : ConsentInputModel 10 | { 11 | public string ClientName { get; set; } 12 | public string ClientUrl { get; set; } 13 | public string ClientLogoUrl { get; set; } 14 | public bool AllowRememberConsent { get; set; } 15 | 16 | public IEnumerable IdentityScopes { get; set; } 17 | public IEnumerable ResourceScopes { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Host/Quickstart/Consent/ProcessConsentResult.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.Quickstart.UI 6 | { 7 | public class ProcessConsentResult 8 | { 9 | public bool IsRedirect => RedirectUri != null; 10 | public string RedirectUri { get; set; } 11 | 12 | public bool ShowView => ViewModel != null; 13 | public ConsentViewModel ViewModel { get; set; } 14 | 15 | public bool HasValidationError => ValidationError != null; 16 | public string ValidationError { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Host/Quickstart/Consent/ScopeViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.Quickstart.UI 6 | { 7 | public class ScopeViewModel 8 | { 9 | public string Name { get; set; } 10 | public string DisplayName { get; set; } 11 | public string Description { get; set; } 12 | public bool Emphasize { get; set; } 13 | public bool Required { get; set; } 14 | public bool Checked { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Host/Quickstart/Grants/GrantsController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.Services; 6 | using IdentityServer4.Stores; 7 | using Microsoft.AspNetCore.Mvc; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | using Microsoft.AspNetCore.Authorization; 12 | 13 | namespace IdentityServer4.Quickstart.UI 14 | { 15 | /// 16 | /// This sample controller allows a user to revoke grants given to clients 17 | /// 18 | [SecurityHeaders] 19 | [Authorize(AuthenticationSchemes = IdentityServer4.IdentityServerConstants.DefaultCookieAuthenticationScheme)] 20 | public class GrantsController : Controller 21 | { 22 | private readonly IIdentityServerInteractionService _interaction; 23 | private readonly IClientStore _clients; 24 | private readonly IResourceStore _resources; 25 | 26 | public GrantsController(IIdentityServerInteractionService interaction, 27 | IClientStore clients, 28 | IResourceStore resources) 29 | { 30 | _interaction = interaction; 31 | _clients = clients; 32 | _resources = resources; 33 | } 34 | 35 | /// 36 | /// Show list of grants 37 | /// 38 | [HttpGet] 39 | public async Task Index() 40 | { 41 | return View("Index", await BuildViewModelAsync()); 42 | } 43 | 44 | /// 45 | /// Handle postback to revoke a client 46 | /// 47 | [HttpPost] 48 | [ValidateAntiForgeryToken] 49 | public async Task Revoke(string clientId) 50 | { 51 | await _interaction.RevokeUserConsentAsync(clientId); 52 | return RedirectToAction("Index"); 53 | } 54 | 55 | private async Task BuildViewModelAsync() 56 | { 57 | var grants = await _interaction.GetAllUserGrantsAsync(); 58 | 59 | var list = new List(); 60 | foreach(var grant in grants) 61 | { 62 | var client = await _clients.FindClientByIdAsync(grant.ClientId); 63 | if (client != null) 64 | { 65 | var resources = await _resources.FindResourcesByScopeAsync(grant.Scopes); 66 | 67 | var item = new GrantViewModel() 68 | { 69 | ClientId = client.ClientId, 70 | ClientName = client.ClientName ?? client.ClientId, 71 | ClientLogoUrl = client.LogoUri, 72 | ClientUrl = client.ClientUri, 73 | Created = grant.CreationTime, 74 | Expires = grant.Expiration, 75 | IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(), 76 | ApiGrantNames = resources.ApiResources.Select(x => x.DisplayName ?? x.Name).ToArray() 77 | }; 78 | 79 | list.Add(item); 80 | } 81 | } 82 | 83 | return new GrantsViewModel 84 | { 85 | Grants = list 86 | }; 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/Host/Quickstart/Grants/GrantsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace IdentityServer4.Quickstart.UI 5 | { 6 | public class GrantsViewModel 7 | { 8 | public IEnumerable Grants { get; set; } 9 | } 10 | 11 | public class GrantViewModel 12 | { 13 | public string ClientId { get; set; } 14 | public string ClientName { get; set; } 15 | public string ClientUrl { get; set; } 16 | public string ClientLogoUrl { get; set; } 17 | public DateTime Created { get; set; } 18 | public DateTime? Expires { get; set; } 19 | public IEnumerable IdentityGrantNames { get; set; } 20 | public IEnumerable ApiGrantNames { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Host/Quickstart/Home/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.Models; 6 | 7 | namespace IdentityServer4.Quickstart.UI 8 | { 9 | public class ErrorViewModel 10 | { 11 | public ErrorMessage Error { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Host/Quickstart/Home/HomeController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.Services; 6 | using Microsoft.AspNetCore.Mvc; 7 | using System.Threading.Tasks; 8 | 9 | namespace IdentityServer4.Quickstart.UI 10 | { 11 | [SecurityHeaders] 12 | public class HomeController : Controller 13 | { 14 | private readonly IIdentityServerInteractionService _interaction; 15 | 16 | public HomeController(IIdentityServerInteractionService interaction) 17 | { 18 | _interaction = interaction; 19 | } 20 | 21 | public IActionResult Index() 22 | { 23 | return View(); 24 | } 25 | 26 | /// 27 | /// Shows the error page 28 | /// 29 | public async Task Error(string errorId) 30 | { 31 | var vm = new ErrorViewModel(); 32 | 33 | // retrieve error details from identityserver 34 | var message = await _interaction.GetErrorContextAsync(errorId); 35 | if (message != null) 36 | { 37 | vm.Error = message; 38 | } 39 | 40 | return View("Error", vm); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/Host/Quickstart/SecurityHeadersAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.Filters; 7 | 8 | namespace IdentityServer4.Quickstart.UI 9 | { 10 | public class SecurityHeadersAttribute : ActionFilterAttribute 11 | { 12 | public override void OnResultExecuting(ResultExecutingContext context) 13 | { 14 | var result = context.Result; 15 | if (result is ViewResult) 16 | { 17 | if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options")) 18 | { 19 | context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff"); 20 | } 21 | if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options")) 22 | { 23 | context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); 24 | } 25 | 26 | var csp = "default-src 'self';"; 27 | // an example if you need client images to be displayed from twitter 28 | //var csp = "default-src 'self'; img-src 'self' https://pbs.twimg.com"; 29 | 30 | // once for standards compliant browsers 31 | if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy")) 32 | { 33 | context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp); 34 | } 35 | // and once again for IE 36 | if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy")) 37 | { 38 | context.HttpContext.Response.Headers.Add("X-Content-Security-Policy", csp); 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Host/Quickstart/TestUsers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityModel; 6 | using IdentityServer4.Test; 7 | using System.Collections.Generic; 8 | using System.Security.Claims; 9 | 10 | namespace IdentityServer4.Quickstart.UI 11 | { 12 | public class TestUsers 13 | { 14 | public static List Users = new List 15 | { 16 | new TestUser{SubjectId = "818727", Username = "alice", Password = "alice", 17 | Claims = 18 | { 19 | new Claim(JwtClaimTypes.Name, "Alice Smith"), 20 | new Claim(JwtClaimTypes.GivenName, "Alice"), 21 | new Claim(JwtClaimTypes.FamilyName, "Smith"), 22 | new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"), 23 | new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), 24 | new Claim(JwtClaimTypes.WebSite, "http://alice.com"), 25 | new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json) 26 | } 27 | }, 28 | new TestUser{SubjectId = "88421113", Username = "bob", Password = "bob", 29 | Claims = 30 | { 31 | new Claim(JwtClaimTypes.Name, "Bob Smith"), 32 | new Claim(JwtClaimTypes.GivenName, "Bob"), 33 | new Claim(JwtClaimTypes.FamilyName, "Smith"), 34 | new Claim(JwtClaimTypes.Email, "BobSmith@email.com"), 35 | new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), 36 | new Claim(JwtClaimTypes.WebSite, "http://bob.com"), 37 | new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json), 38 | new Claim("location", "somewhere") 39 | } 40 | } 41 | }; 42 | } 43 | } -------------------------------------------------------------------------------- /src/Host/Startup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using Host.Configuration; 6 | using IdentityServer4; 7 | using IdentityServer4.MongoDB.Interfaces; 8 | using IdentityServer4.MongoDB.Mappers; 9 | using IdentityServer4.Quickstart.UI; 10 | using Microsoft.AspNetCore.Builder; 11 | using Microsoft.AspNetCore.Http; 12 | using Microsoft.Extensions.Configuration; 13 | using Microsoft.Extensions.DependencyInjection; 14 | using Microsoft.IdentityModel.Tokens; 15 | using System.Linq; 16 | 17 | namespace Host 18 | { 19 | public class Startup 20 | { 21 | private readonly IConfiguration _config; 22 | 23 | public Startup(IConfiguration config) 24 | { 25 | _config = config; 26 | } 27 | 28 | public void ConfigureServices(IServiceCollection services) 29 | { 30 | services.AddMvc(); 31 | 32 | services.Configure(iis => 33 | { 34 | iis.AuthenticationDisplayName = "Windows"; 35 | iis.AutomaticAuthentication = false; 36 | }); 37 | 38 | services.AddIdentityServer(options => 39 | { 40 | options.Events.RaiseSuccessEvents = true; 41 | options.Events.RaiseFailureEvents = true; 42 | options.Events.RaiseErrorEvents = true; 43 | }) 44 | .AddConfigurationStore(_config.GetSection("MongoDB")) 45 | .AddOperationalStore(_config.GetSection("MongoDB"), 46 | (tco) => 47 | { 48 | tco.Enable = true; 49 | tco.Interval = 3600; 50 | }) 51 | .AddDeveloperSigningCredential() 52 | .AddExtensionGrantValidator() 53 | .AddExtensionGrantValidator() 54 | .AddJwtBearerClientAuthentication() 55 | .AddAppAuthRedirectUriValidator() 56 | .AddTestUsers(TestUsers.Users); 57 | 58 | services.AddMvc(option => option.EnableEndpointRouting = false); 59 | 60 | services.AddExternalIdentityProviders(); 61 | } 62 | 63 | public void Configure(IApplicationBuilder app) 64 | { 65 | app.UseDeveloperExceptionPage(); 66 | 67 | // Setup Databases 68 | using (var serviceScope = app.ApplicationServices.GetRequiredService().CreateScope()) 69 | { 70 | EnsureSeedData(serviceScope.ServiceProvider.GetService()); 71 | } 72 | 73 | app.UseIdentityServer(); 74 | 75 | app.UseStaticFiles(); 76 | app.UseMvcWithDefaultRoute(); 77 | } 78 | 79 | private static void EnsureSeedData(IConfigurationDbContext context) 80 | { 81 | if (!context.Clients.Any()) 82 | { 83 | foreach (var client in Clients.Get().ToList()) 84 | { 85 | context.AddClient(client.ToEntity()); 86 | } 87 | } 88 | 89 | if (!context.IdentityResources.Any()) 90 | { 91 | foreach (var resource in Resources.GetIdentityResources().ToList()) 92 | { 93 | context.AddIdentityResource(resource.ToEntity()); 94 | } 95 | } 96 | 97 | if (!context.ApiResources.Any()) 98 | { 99 | foreach (var resource in Resources.GetApiResources().ToList()) 100 | { 101 | context.AddApiResource(resource.ToEntity()); 102 | } 103 | } 104 | 105 | if (!context.ApiScopes.Any()) 106 | { 107 | foreach (var resource in Resources.GetApiScopes().ToList()) 108 | { 109 | context.AddApiScope(resource.ToEntity()); 110 | } 111 | } 112 | } 113 | } 114 | 115 | public static class ServiceExtensions 116 | { 117 | public static IServiceCollection AddExternalIdentityProviders(this IServiceCollection services) 118 | { 119 | // configures the OpenIdConnect handlers to persist the state parameter into the server-side IDistributedCache. 120 | services.AddOidcStateDataFormatterCache("aad", "demoidsrv"); 121 | 122 | services.AddAuthentication() 123 | .AddGoogle("Google", options => 124 | { 125 | options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; 126 | 127 | options.ClientId = "708996912208-9m4dkjb5hscn7cjrn5u0r4tbgkbj1fko.apps.googleusercontent.com"; 128 | options.ClientSecret = "wdfPY6t8H8cecgjlxud__4Gh"; 129 | }) 130 | .AddOpenIdConnect("demoidsrv", "IdentityServer", options => 131 | { 132 | options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; 133 | options.SignOutScheme = IdentityServerConstants.SignoutScheme; 134 | 135 | options.Authority = "https://demo.identityserver.io/"; 136 | options.ClientId = "implicit"; 137 | options.ResponseType = "id_token"; 138 | options.SaveTokens = true; 139 | options.CallbackPath = new PathString("/signin-idsrv"); 140 | options.SignedOutCallbackPath = new PathString("/signout-callback-idsrv"); 141 | options.RemoteSignOutPath = new PathString("/signout-idsrv"); 142 | 143 | options.TokenValidationParameters = new TokenValidationParameters 144 | { 145 | NameClaimType = "name", 146 | RoleClaimType = "role" 147 | }; 148 | }) 149 | .AddOpenIdConnect("aad", "Azure AD", options => 150 | { 151 | options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; 152 | options.SignOutScheme = IdentityServerConstants.SignoutScheme; 153 | 154 | options.Authority = "https://login.windows.net/4ca9cb4c-5e5f-4be9-b700-c532992a3705"; 155 | options.ClientId = "96e3c53e-01cb-4244-b658-a42164cb67a9"; 156 | options.ResponseType = "id_token"; 157 | options.CallbackPath = new PathString("/signin-aad"); 158 | options.SignedOutCallbackPath = new PathString("/signout-callback-aad"); 159 | options.RemoteSignOutPath = new PathString("/signout-aad"); 160 | options.TokenValidationParameters = new TokenValidationParameters 161 | { 162 | NameClaimType = "name", 163 | RoleClaimType = "role" 164 | }; 165 | }) 166 | .AddOpenIdConnect("adfs", "ADFS", options => 167 | { 168 | options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; 169 | options.SignOutScheme = IdentityServerConstants.SignoutScheme; 170 | 171 | options.Authority = "https://adfs.leastprivilege.vm/adfs"; 172 | options.ClientId = "c0ea8d99-f1e7-43b0-a100-7dee3f2e5c3c"; 173 | options.ResponseType = "id_token"; 174 | 175 | options.CallbackPath = new PathString("/signin-adfs"); 176 | options.SignedOutCallbackPath = new PathString("/signout-callback-adfs"); 177 | options.RemoteSignOutPath = new PathString("/signout-adfs"); 178 | options.TokenValidationParameters = new TokenValidationParameters 179 | { 180 | NameClaimType = "name", 181 | RoleClaimType = "role" 182 | }; 183 | }); 184 | 185 | return services; 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/Host/Views/Account/LoggedOut.cshtml: -------------------------------------------------------------------------------- 1 | @model LoggedOutViewModel 2 | 3 | @{ 4 | // set this so the layout rendering sees an anonymous user 5 | ViewData["signed-out"] = true; 6 | } 7 | 8 | 27 | 28 | @section scripts 29 | { 30 | @if (Model.AutomaticRedirectAfterSignOut) 31 | { 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Host/Views/Account/Login.cshtml: -------------------------------------------------------------------------------- 1 | @model LoginViewModel 2 | 3 | -------------------------------------------------------------------------------- /src/Host/Views/Account/Logout.cshtml: -------------------------------------------------------------------------------- 1 | @model LogoutViewModel 2 | 3 |
4 | 7 | 8 |
9 |
10 |

Would you like to logout of IdentityServer?

11 |
12 | 13 |
14 |
15 | 16 |
17 |
18 |
19 |
20 |
21 |
-------------------------------------------------------------------------------- /src/Host/Views/Consent/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model ConsentViewModel 2 | 3 | -------------------------------------------------------------------------------- /src/Host/Views/Consent/_ScopeListItem.cshtml: -------------------------------------------------------------------------------- 1 | @model ScopeViewModel 2 | 3 |
  • 4 | 24 | @if (Model.Required) 25 | { 26 | (required) 27 | } 28 | @if (Model.Description != null) 29 | { 30 | 33 | } 34 |
  • -------------------------------------------------------------------------------- /src/Host/Views/Grants/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model GrantsViewModel 2 | 3 |
    4 | 12 | 13 | @if (Model.Grants.Any() == false) 14 | { 15 |
    16 |
    17 |
    18 | You have not given access to any applications 19 |
    20 |
    21 |
    22 | } 23 | else 24 | { 25 | foreach (var grant in Model.Grants) 26 | { 27 |
    28 |
    29 | @if (grant.ClientLogoUrl != null) 30 | { 31 | 32 | } 33 |
    34 |
    35 |
    @grant.ClientName
    36 |
    37 | Created: @grant.Created.ToString("yyyy-MM-dd") 38 |
    39 | @if (grant.Expires.HasValue) 40 | { 41 |
    42 | Expires: @grant.Expires.Value.ToString("yyyy-MM-dd") 43 |
    44 | } 45 | @if (grant.IdentityGrantNames.Any()) 46 | { 47 |
    48 |
    Identity Grants
    49 |
      50 | @foreach (var name in grant.IdentityGrantNames) 51 | { 52 |
    • @name
    • 53 | } 54 |
    55 |
    56 | } 57 | @if (grant.ApiGrantNames.Any()) 58 | { 59 |
    60 |
    API Grants
    61 |
      62 | @foreach (var name in grant.ApiGrantNames) 63 | { 64 |
    • @name
    • 65 | } 66 |
    67 |
    68 | } 69 |
    70 |
    71 |
    72 | 73 | 74 |
    75 |
    76 |
    77 | } 78 | } 79 |
    -------------------------------------------------------------------------------- /src/Host/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | 
    2 | 11 | 12 |
    13 |
    14 |

    15 | IdentityServer publishes a 16 | discovery document 17 | where you can find metadata and links to all the endpoints, key material, etc. 18 |

    19 |
    20 |
    21 |

    22 | Click here to manage your stored grants. 23 |

    24 |
    25 |
    26 |
    27 |
    28 |

    29 | Here are links to the 30 | source code repository, 31 | and ready to use samples. 32 |

    33 |
    34 |
    35 |
    36 | -------------------------------------------------------------------------------- /src/Host/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Hosting 2 | @using Microsoft.Extensions.Hosting 3 | @model ErrorViewModel 4 | @inject IWebHostEnvironment host 5 | 6 | @{ 7 | var error = Model?.Error?.Error; 8 | var errorDescription = host.IsDevelopment() ? Model?.Error?.ErrorDescription : null; 9 | var request_id = Model?.Error?.RequestId; 10 | } 11 | 12 |
    13 | 16 | 17 |
    18 |
    19 |
    20 | Sorry, there was an error 21 | 22 | @if (error != null) 23 | { 24 | 25 | 26 | : @error 27 | 28 | 29 | 30 | if (errorDescription != null) 31 | { 32 |
    @errorDescription
    33 | } 34 | } 35 |
    36 | 37 | @if (request_id != null) 38 | { 39 |
    Request Id: @request_id
    40 | } 41 |
    42 |
    43 |
    44 | -------------------------------------------------------------------------------- /src/Host/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @using IdentityServer4.Extensions 2 | @{ 3 | string name = null; 4 | if (!true.Equals(ViewData["signed-out"])) 5 | { 6 | name = Context.User?.FindFirst("name")?.Value; 7 | } 8 | } 9 | 10 | 11 | 12 | 13 | 14 | 15 | IdentityServer4 16 | 17 | 18 | 19 | 20 | 21 | 22 | 52 | 53 |
    54 | @RenderBody() 55 |
    56 | 57 | 58 | 59 | @RenderSection("scripts", required: false) 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/Host/Views/Shared/_ValidationSummary.cshtml: -------------------------------------------------------------------------------- 1 | @if (ViewContext.ModelState.IsValid == false) 2 | { 3 |
    4 | Error 5 |
    6 |
    7 | } -------------------------------------------------------------------------------- /src/Host/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using IdentityServer4.Quickstart.UI 2 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 3 | -------------------------------------------------------------------------------- /src/Host/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /src/Host/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "MongoDB": { 3 | "ConnectionString": "mongodb://localhost:27017", 4 | "Database": "IS4" 5 | }, 6 | "Clients": [ 7 | { 8 | "ClientId": "client", 9 | "ClientSecrets": [ { "Value": "K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=" } ], 10 | "AllowedGrantTypes": [ "client_credentials" ], 11 | "AllowedScopes": [ "api1", "api2.read_only" ], 12 | "Properties": {"foo": "bar" } 13 | }, 14 | { 15 | "ClientId": "hybrid", 16 | "ClientSecrets": [ { "Value": "K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=" } ], 17 | "AllowedGrantTypes": [ "hybrid", "client_credentials" ], 18 | "AllowedScopes": [ "openid", "profile", "api1", "api2.read_only" ] 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /src/Host/web.config: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Host/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 65px; 3 | } 4 | .navbar-header { 5 | position: relative; 6 | top: -4px; 7 | } 8 | .navbar-brand > .icon-banner { 9 | position: relative; 10 | top: -2px; 11 | display: inline; 12 | } 13 | .icon { 14 | position: relative; 15 | top: -10px; 16 | } 17 | .logged-out iframe { 18 | display: none; 19 | width: 0; 20 | height: 0; 21 | } 22 | .page-consent .client-logo { 23 | float: left; 24 | } 25 | .page-consent .client-logo img { 26 | width: 80px; 27 | height: 80px; 28 | } 29 | .page-consent .consent-buttons { 30 | margin-top: 25px; 31 | } 32 | .page-consent .consent-form .consent-scopecheck { 33 | display: inline-block; 34 | margin-right: 5px; 35 | } 36 | .page-consent .consent-form .consent-description { 37 | margin-left: 25px; 38 | } 39 | .page-consent .consent-form .consent-description label { 40 | font-weight: normal; 41 | } 42 | .page-consent .consent-form .consent-remember { 43 | padding-left: 16px; 44 | } 45 | .grants .page-header { 46 | margin-bottom: 10px; 47 | } 48 | .grants .grant { 49 | margin-top: 20px; 50 | padding-bottom: 20px; 51 | border-bottom: 1px solid lightgray; 52 | } 53 | .grants .grant img { 54 | width: 100px; 55 | height: 100px; 56 | } 57 | .grants .grant .clientname { 58 | font-size: 140%; 59 | font-weight: bold; 60 | } 61 | .grants .grant .granttype { 62 | font-size: 120%; 63 | font-weight: bold; 64 | } 65 | .grants .grant .created { 66 | font-size: 120%; 67 | font-weight: bold; 68 | } 69 | .grants .grant .expires { 70 | font-size: 120%; 71 | font-weight: bold; 72 | } 73 | .grants .grant li { 74 | list-style-type: none; 75 | display: inline; 76 | } 77 | .grants .grant li:after { 78 | content: ', '; 79 | } 80 | .grants .grant li:last-child:after { 81 | content: ''; 82 | } -------------------------------------------------------------------------------- /src/Host/wwwroot/css/site.less: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 65px; 3 | } 4 | 5 | .navbar-header { 6 | position: relative; 7 | top: -4px; 8 | } 9 | 10 | .navbar-brand > .icon-banner { 11 | position: relative; 12 | top: -2px; 13 | display: inline; 14 | } 15 | 16 | .icon { 17 | position: relative; 18 | top: -10px; 19 | } 20 | 21 | .logged-out iframe { 22 | display: none; 23 | width: 0; 24 | height: 0; 25 | } 26 | 27 | .page-consent { 28 | .client-logo { 29 | float: left; 30 | 31 | img { 32 | width: 80px; 33 | height: 80px; 34 | } 35 | } 36 | 37 | .consent-buttons { 38 | margin-top: 25px; 39 | } 40 | 41 | .consent-form { 42 | .consent-scopecheck { 43 | display: inline-block; 44 | margin-right: 5px; 45 | } 46 | 47 | .consent-scopecheck[disabled] { 48 | //visibility:hidden; 49 | } 50 | 51 | .consent-description { 52 | margin-left: 25px; 53 | 54 | label { 55 | font-weight: normal; 56 | } 57 | } 58 | 59 | .consent-remember { 60 | padding-left: 16px; 61 | } 62 | } 63 | } 64 | 65 | .grants { 66 | .page-header { 67 | margin-bottom: 10px; 68 | } 69 | 70 | .grant { 71 | margin-top: 20px; 72 | padding-bottom: 20px; 73 | border-bottom: 1px solid lightgray; 74 | 75 | img { 76 | width: 100px; 77 | height: 100px; 78 | } 79 | 80 | .clientname { 81 | font-size: 140%; 82 | font-weight: bold; 83 | } 84 | 85 | .granttype { 86 | font-size: 120%; 87 | font-weight: bold; 88 | } 89 | 90 | .created { 91 | font-size: 120%; 92 | font-weight: bold; 93 | } 94 | 95 | .expires { 96 | font-size: 120%; 97 | font-weight: bold; 98 | } 99 | 100 | li { 101 | list-style-type: none; 102 | display: inline; 103 | 104 | &:after { 105 | content: ', '; 106 | } 107 | 108 | &:last-child:after { 109 | content: ''; 110 | } 111 | 112 | .displayname { 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Host/wwwroot/css/site.min.css: -------------------------------------------------------------------------------- 1 | body{margin-top:65px;}.navbar-header{position:relative;top:-4px;}.navbar-brand>.icon-banner{position:relative;top:-2px;display:inline;}.icon{position:relative;top:-10px;}.logged-out iframe{display:none;width:0;height:0;}.page-consent .client-logo{float:left;}.page-consent .client-logo img{width:80px;height:80px;}.page-consent .consent-buttons{margin-top:25px;}.page-consent .consent-form .consent-scopecheck{display:inline-block;margin-right:5px;}.page-consent .consent-form .consent-description{margin-left:25px;}.page-consent .consent-form .consent-description label{font-weight:normal;}.page-consent .consent-form .consent-remember{padding-left:16px;}.grants .page-header{margin-bottom:10px;}.grants .grant{margin-top:20px;padding-bottom:20px;border-bottom:1px solid #d3d3d3;}.grants .grant img{width:100px;height:100px;}.grants .grant .clientname{font-size:140%;font-weight:bold;}.grants .grant .granttype{font-size:120%;font-weight:bold;}.grants .grant .created{font-size:120%;font-weight:bold;}.grants .grant .expires{font-size:120%;font-weight:bold;}.grants .grant li{list-style-type:none;display:inline;}.grants .grant li:after{content:', ';}.grants .grant li:last-child:after{content:'';} -------------------------------------------------------------------------------- /src/Host/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diogodamiani/IdentityServer4.Contrib.MongoDB/48744f98d520bbb341e9ccd6b8c357a16dbed825/src/Host/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/Host/wwwroot/icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diogodamiani/IdentityServer4.Contrib.MongoDB/48744f98d520bbb341e9ccd6b8c357a16dbed825/src/Host/wwwroot/icon.jpg -------------------------------------------------------------------------------- /src/Host/wwwroot/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diogodamiani/IdentityServer4.Contrib.MongoDB/48744f98d520bbb341e9ccd6b8c357a16dbed825/src/Host/wwwroot/icon.png -------------------------------------------------------------------------------- /src/Host/wwwroot/js/signout-redirect.js: -------------------------------------------------------------------------------- 1 | window.addEventListener("load", function () { 2 | var a = document.querySelector("a.PostLogoutRedirectUri"); 3 | if (a) { 4 | window.location = a.href; 5 | } 6 | }); 7 | -------------------------------------------------------------------------------- /src/Host/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diogodamiani/IdentityServer4.Contrib.MongoDB/48744f98d520bbb341e9ccd6b8c357a16dbed825/src/Host/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/Host/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diogodamiani/IdentityServer4.Contrib.MongoDB/48744f98d520bbb341e9ccd6b8c357a16dbed825/src/Host/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/Host/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diogodamiani/IdentityServer4.Contrib.MongoDB/48744f98d520bbb341e9ccd6b8c357a16dbed825/src/Host/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/Host/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diogodamiani/IdentityServer4.Contrib.MongoDB/48744f98d520bbb341e9ccd6b8c357a16dbed825/src/Host/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Configuration/MongoDBConfiguration.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Driver; 2 | 3 | namespace IdentityServer4.MongoDB.Configuration 4 | { 5 | public class MongoDBConfiguration 6 | { 7 | public string ConnectionString { get; set; } 8 | public string Database { get; set; } 9 | public SslSettings SslSettings { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Constants.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.MongoDB 6 | { 7 | public class Constants 8 | { 9 | public class TableNames 10 | { 11 | // Configuration 12 | public const string IdentityResource = "IdentityResources"; 13 | public const string IdentityClaim = "IdentityClaims"; 14 | 15 | public const string ApiResource = "ApiResources"; 16 | public const string ApiSecret = "ApiSecrets"; 17 | public const string ApiScope = "ApiScopes"; 18 | public const string ApiClaim = "ApiClaims"; 19 | public const string ApiScopeClaim = "ApiScopeClaims"; 20 | 21 | public const string Client = "Clients"; 22 | public const string ClientGrantType = "ClientGrantTypes"; 23 | public const string ClientRedirectUri = "ClientRedirectUris"; 24 | public const string ClientPostLogoutRedirectUri = "ClientPostLogoutRedirectUris"; 25 | public const string ClientScopes = "ClientScopes"; 26 | public const string ClientSecret = "ClientSecrets"; 27 | public const string ClientClaim = "ClientClaims"; 28 | public const string ClientIdPRestriction = "ClientIdPRestrictions"; 29 | public const string ClientCorsOrigin = "ClientCorsOrigins"; 30 | 31 | // Operational 32 | public const string PersistedGrant = "PersistedGrants"; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/DbContexts/ConfigurationDbContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | using IdentityServer4.MongoDB.Configuration; 5 | using IdentityServer4.MongoDB.Entities; 6 | using IdentityServer4.MongoDB.Interfaces; 7 | using Microsoft.Extensions.Options; 8 | using MongoDB.Driver; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | 12 | namespace IdentityServer4.MongoDB.DbContexts 13 | { 14 | public class ConfigurationDbContext : MongoDBContextBase, IConfigurationDbContext 15 | { 16 | private readonly IMongoCollection _clients; 17 | private readonly IMongoCollection _identityResources; 18 | private readonly IMongoCollection _apiResources; 19 | private readonly IMongoCollection _apiScopes; 20 | 21 | public ConfigurationDbContext(IOptions settings) 22 | : base(settings) 23 | { 24 | _clients = Database.GetCollection(Constants.TableNames.Client); 25 | _identityResources = Database.GetCollection(Constants.TableNames.IdentityResource); 26 | _apiResources = Database.GetCollection(Constants.TableNames.ApiResource); 27 | _apiScopes = Database.GetCollection(Constants.TableNames.ApiScope); 28 | 29 | CreateClientsIndexes(); 30 | CreateIdentityResourcesIndexes(); 31 | CreateApiResourcesIndexes(); 32 | CreateApiScopesIndexes(); 33 | } 34 | 35 | private void CreateClientsIndexes() 36 | { 37 | var indexOptions = new CreateIndexOptions() { Background = true }; 38 | 39 | var builder = Builders.IndexKeys; 40 | var clientIdIndexModel = new CreateIndexModel(builder.Ascending(_ => _.ClientId), indexOptions); 41 | _clients.Indexes.CreateOne(clientIdIndexModel); 42 | } 43 | 44 | private void CreateIdentityResourcesIndexes() 45 | { 46 | var indexOptions = new CreateIndexOptions() { Background = true }; 47 | 48 | var builder = Builders.IndexKeys; 49 | var nameIndexModel = new CreateIndexModel(builder.Ascending(_ => _.Name), indexOptions); 50 | _identityResources.Indexes.CreateOne(nameIndexModel); 51 | } 52 | 53 | private void CreateApiResourcesIndexes() 54 | { 55 | var indexOptions = new CreateIndexOptions() { Background = true }; 56 | 57 | var builder = Builders.IndexKeys; 58 | var nameIndexModel = new CreateIndexModel(builder.Ascending(_ => _.Name), indexOptions); 59 | var scopesIndexModel = new CreateIndexModel(builder.Ascending(_ => _.Scopes), indexOptions); 60 | _apiResources.Indexes.CreateOne(nameIndexModel); 61 | _apiResources.Indexes.CreateOne(scopesIndexModel); 62 | } 63 | 64 | private void CreateApiScopesIndexes() 65 | { 66 | var indexOptions = new CreateIndexOptions { Background = true }; 67 | 68 | var builder = Builders.IndexKeys; 69 | var nameIndexModel = new CreateIndexModel(builder.Ascending(_ => _.Name), indexOptions); 70 | _apiScopes.Indexes.CreateOne(nameIndexModel); 71 | } 72 | 73 | public IQueryable Clients => _clients.AsQueryable(); 74 | 75 | public IQueryable IdentityResources => _identityResources.AsQueryable(); 76 | 77 | public IQueryable ApiResources => _apiResources.AsQueryable(); 78 | 79 | public IQueryable ApiScopes => _apiScopes.AsQueryable(); 80 | 81 | public async Task AddClient(Client entity) 82 | { 83 | await _clients.InsertOneAsync(entity); 84 | } 85 | 86 | public async Task AddIdentityResource(IdentityResource entity) 87 | { 88 | await _identityResources.InsertOneAsync(entity); 89 | } 90 | 91 | public async Task AddApiResource(ApiResource entity) 92 | { 93 | await _apiResources.InsertOneAsync(entity); 94 | } 95 | 96 | public async Task AddApiScope(ApiScope entity) 97 | { 98 | await this._apiScopes.InsertOneAsync(entity); 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/DbContexts/MongoDBContextBase.cs: -------------------------------------------------------------------------------- 1 | using IdentityServer4.MongoDB.Configuration; 2 | using Microsoft.Extensions.Options; 3 | using MongoDB.Driver; 4 | using System; 5 | 6 | namespace IdentityServer4.MongoDB.DbContexts 7 | { 8 | public class MongoDBContextBase : IDisposable 9 | { 10 | private readonly IMongoClient _client; 11 | 12 | public MongoDBContextBase(IOptions settings) 13 | { 14 | if (settings.Value == null) 15 | throw new ArgumentNullException(nameof(settings), "MongoDBConfiguration cannot be null."); 16 | 17 | if (settings.Value.ConnectionString == null) 18 | throw new ArgumentNullException(nameof(settings), "MongoDBConfiguration.ConnectionString cannot be null."); 19 | 20 | var mongoUrl = MongoUrl.Create(settings.Value.ConnectionString); 21 | 22 | if (settings.Value.Database == null && mongoUrl.DatabaseName == null) 23 | throw new ArgumentNullException(nameof(settings), "MongoDBConfiguration.Database cannot be null."); 24 | 25 | var clientSettings = MongoClientSettings.FromUrl(mongoUrl); 26 | 27 | if (settings.Value.SslSettings != null) 28 | { 29 | clientSettings.SslSettings = settings.Value.SslSettings; 30 | clientSettings.UseTls = true; 31 | } 32 | 33 | _client = new MongoClient(clientSettings); 34 | Database = _client.GetDatabase(settings.Value.Database ?? mongoUrl.DatabaseName); 35 | } 36 | 37 | protected IMongoDatabase Database { get; } 38 | 39 | public void Dispose() 40 | { 41 | // TODO 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/DbContexts/PersistedGrantDbContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.MongoDB.Configuration; 6 | using IdentityServer4.MongoDB.Entities; 7 | using IdentityServer4.MongoDB.Interfaces; 8 | using Microsoft.Extensions.Options; 9 | using MongoDB.Driver; 10 | using System; 11 | using System.Linq; 12 | using System.Linq.Expressions; 13 | using System.Threading.Tasks; 14 | 15 | namespace IdentityServer4.MongoDB.DbContexts 16 | { 17 | public class PersistedGrantDbContext : MongoDBContextBase, IPersistedGrantDbContext 18 | { 19 | private readonly IMongoCollection _persistedGrants; 20 | 21 | public PersistedGrantDbContext(IOptions settings) 22 | : base(settings) 23 | { 24 | _persistedGrants = Database.GetCollection(Constants.TableNames.PersistedGrant); 25 | CreateIndexes(); 26 | } 27 | 28 | private void CreateIndexes() 29 | { 30 | var indexOptions = new CreateIndexOptions() { Background = true }; 31 | var builder = Builders.IndexKeys; 32 | 33 | var keyIndexModel = new CreateIndexModel(builder.Ascending(_ => _.Key), indexOptions); 34 | var subIndexModel = new CreateIndexModel(builder.Ascending(_ => _.SubjectId), indexOptions); 35 | var clientIdSubIndexModel = new CreateIndexModel( 36 | builder.Combine( 37 | builder.Ascending(_ => _.ClientId), 38 | builder.Ascending(_ => _.SubjectId)), 39 | indexOptions); 40 | 41 | var clientIdSubTypeIndexModel = new CreateIndexModel( 42 | builder.Combine( 43 | builder.Ascending(_ => _.ClientId), 44 | builder.Ascending(_ => _.SubjectId), 45 | builder.Ascending(_ => _.Type)), 46 | indexOptions); 47 | 48 | _persistedGrants.Indexes.CreateOne(keyIndexModel); 49 | _persistedGrants.Indexes.CreateOne(subIndexModel); 50 | _persistedGrants.Indexes.CreateOne(clientIdSubIndexModel); 51 | _persistedGrants.Indexes.CreateOne(clientIdSubTypeIndexModel); 52 | } 53 | 54 | public IQueryable PersistedGrants 55 | { 56 | get { return _persistedGrants.AsQueryable(); } 57 | } 58 | 59 | public Task Remove(Expression> filter) 60 | { 61 | return _persistedGrants.DeleteManyAsync(filter); 62 | } 63 | 64 | public Task RemoveExpired() 65 | { 66 | return Remove(x => x.Expiration < DateTime.UtcNow); 67 | } 68 | 69 | public Task InsertOrUpdate(Expression> filter, PersistedGrant entity) 70 | { 71 | return _persistedGrants.ReplaceOneAsync(filter, entity, new ReplaceOptions() {IsUpsert = true}); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/ApiResource.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace IdentityServer4.MongoDB.Entities 7 | { 8 | public class ApiResource 9 | { 10 | public bool Enabled { get; set; } = true; 11 | public string Name { get; set; } 12 | public string DisplayName { get; set; } 13 | public string Description { get; set; } 14 | public IDictionary Properties { get; set; } 15 | 16 | public List Secrets { get; set; } 17 | public List Scopes { get; set; } 18 | public List UserClaims { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/ApiResourceClaim.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.MongoDB.Entities 6 | { 7 | public class ApiResourceClaim : UserClaim 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/ApiScope.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace IdentityServer4.MongoDB.Entities 7 | { 8 | public class ApiScope 9 | { 10 | public string Name { get; set; } 11 | public string DisplayName { get; set; } 12 | public string Description { get; set; } 13 | public bool Required { get; set; } 14 | public bool Emphasize { get; set; } 15 | public bool ShowInDiscoveryDocument { get; set; } = true; 16 | public List UserClaims { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/ApiScopeClaim.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.MongoDB.Entities 6 | { 7 | public class ApiScopeClaim : UserClaim 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/ApiSecret.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.MongoDB.Entities 6 | { 7 | public class ApiSecret : Secret 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/Client.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | using IdentityServer4.Models; 5 | using System.Collections.Generic; 6 | 7 | using static IdentityServer4.IdentityServerConstants; 8 | 9 | namespace IdentityServer4.MongoDB.Entities 10 | { 11 | public class Client 12 | { 13 | public bool Enabled { get; set; } = true; 14 | public string ClientId { get; set; } 15 | public string ProtocolType { get; set; } = ProtocolTypes.OpenIdConnect; 16 | public List ClientSecrets { get; set; } 17 | public bool RequireClientSecret { get; set; } = true; 18 | public string ClientName { get; set; } 19 | public string Description { get; set; } 20 | public string ClientUri { get; set; } 21 | public string LogoUri { get; set; } 22 | public bool RequireConsent { get; set; } = true; 23 | public bool AllowRememberConsent { get; set; } = true; 24 | public bool AlwaysIncludeUserClaimsInIdToken { get; set; } 25 | public List AllowedGrantTypes { get; set; } 26 | public bool RequirePkce { get; set; } 27 | public bool AllowPlainTextPkce { get; set; } 28 | public bool AllowAccessTokensViaBrowser { get; set; } 29 | public List RedirectUris { get; set; } 30 | public List PostLogoutRedirectUris { get; set; } 31 | public string FrontChannelLogoutUri { get; set; } 32 | public bool FrontChannelLogoutSessionRequired { get; set; } = true; 33 | public string BackChannelLogoutUri { get; set; } 34 | public bool BackChannelLogoutSessionRequired { get; set; } = true; 35 | public bool AllowOfflineAccess { get; set; } 36 | public List AllowedScopes { get; set; } 37 | public int IdentityTokenLifetime { get; set; } = 300; 38 | public int AccessTokenLifetime { get; set; } = 3600; 39 | public int AuthorizationCodeLifetime { get; set; } = 300; 40 | public int? ConsentLifetime { get; set; } = null; 41 | public int AbsoluteRefreshTokenLifetime { get; set; } = 2592000; 42 | public int SlidingRefreshTokenLifetime { get; set; } = 1296000; 43 | public int RefreshTokenUsage { get; set; } = (int)TokenUsage.OneTimeOnly; 44 | public bool UpdateAccessTokenClaimsOnRefresh { get; set; } 45 | public int RefreshTokenExpiration { get; set; } = (int)TokenExpiration.Absolute; 46 | public int AccessTokenType { get; set; } = (int)0; // AccessTokenType.Jwt; 47 | public bool EnableLocalLogin { get; set; } = true; 48 | public List IdentityProviderRestrictions { get; set; } 49 | public bool IncludeJwtId { get; set; } 50 | public List Claims { get; set; } 51 | public bool AlwaysSendClientClaims { get; set; } 52 | public string ClientClaimsPrefix { get; set; } = "client_"; 53 | public string PairWiseSubjectSalt { get; set; } 54 | public List AllowedCorsOrigins { get; set; } 55 | public List Properties { get; set; } 56 | public int? UserSsoLifetime { get; set; } 57 | public string UserCodeType { get; set; } 58 | public int DeviceCodeLifetime { get; set; } 59 | } 60 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/ClientClaim.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.MongoDB.Entities 6 | { 7 | public class ClientClaim 8 | { 9 | public string Type { get; set; } 10 | public string Value { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/ClientCorsOrigin.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.MongoDB.Entities 6 | { 7 | public class ClientCorsOrigin 8 | { 9 | public string Origin { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/ClientGrantType.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.MongoDB.Entities 6 | { 7 | public class ClientGrantType 8 | { 9 | public string GrantType { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/ClientIdPRestriction.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.MongoDB.Entities 6 | { 7 | public class ClientIdPRestriction 8 | { 9 | public string Provider { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/ClientPostLogoutRedirectUri.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.MongoDB.Entities 6 | { 7 | public class ClientPostLogoutRedirectUri 8 | { 9 | public string PostLogoutRedirectUri { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/ClientProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace IdentityServer4.MongoDB.Entities 6 | { 7 | public class ClientProperty 8 | { 9 | public string Key { get; set; } 10 | public string Value { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/ClientRedirectUri.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.MongoDB.Entities 6 | { 7 | public class ClientRedirectUri 8 | { 9 | public string RedirectUri { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/ClientScope.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.MongoDB.Entities 6 | { 7 | public class ClientScope 8 | { 9 | public string Scope { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/ClientSecret.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.MongoDB.Entities 6 | { 7 | public class ClientSecret : Secret 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/IdentityClaim.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.MongoDB.Entities 6 | { 7 | public class IdentityClaim : UserClaim 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/IdentityResource.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace IdentityServer4.MongoDB.Entities 7 | { 8 | public class IdentityResource 9 | { 10 | public bool Enabled { get; set; } = true; 11 | public string Name { get; set; } 12 | public string DisplayName { get; set; } 13 | public string Description { get; set; } 14 | public bool Required { get; set; } 15 | public bool Emphasize { get; set; } 16 | public bool ShowInDiscoveryDocument { get; set; } = true; 17 | public List UserClaims { get; set; } 18 | public IDictionary Properties { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/PersistedGrant.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace IdentityServer4.MongoDB.Entities 7 | { 8 | public class PersistedGrant 9 | { 10 | public string Key { get; set; } 11 | public string Type { get; set; } 12 | public string SubjectId { get; set; } 13 | public string ClientId { get; set; } 14 | public DateTime CreationTime { get; set; } 15 | public DateTime? Expiration { get; set; } 16 | public string Data { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/Secret.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System; 6 | using static IdentityServer4.IdentityServerConstants; 7 | 8 | namespace IdentityServer4.MongoDB.Entities 9 | { 10 | public abstract class Secret 11 | { 12 | public string Description { get; set; } 13 | public string Value { get; set; } 14 | public DateTime? Expiration { get; set; } 15 | public string Type { get; set; } = SecretTypes.SharedSecret; 16 | } 17 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Entities/UserClaim.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.MongoDB.Entities 6 | { 7 | public abstract class UserClaim 8 | { 9 | public int Id { get; set; } 10 | public string Type { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Extensions/IdentityServerMongoDBBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.MongoDB; 6 | using IdentityServer4.MongoDB.Configuration; 7 | using IdentityServer4.MongoDB.DbContexts; 8 | using IdentityServer4.MongoDB.Entities; 9 | using IdentityServer4.MongoDB.Interfaces; 10 | using IdentityServer4.MongoDB.Options; 11 | using IdentityServer4.MongoDB.Services; 12 | using IdentityServer4.MongoDB.Stores; 13 | using IdentityServer4.Services; 14 | using IdentityServer4.Stores; 15 | using Microsoft.Extensions.Configuration; 16 | using Microsoft.Extensions.Hosting; 17 | using MongoDB.Bson.Serialization; 18 | using System; 19 | 20 | namespace Microsoft.Extensions.DependencyInjection 21 | { 22 | public static class IdentityServerMongoDBBuilderExtensions 23 | { 24 | public static IIdentityServerBuilder AddConfigurationStore( 25 | this IIdentityServerBuilder builder, Action setupAction) 26 | { 27 | builder.Services.Configure(setupAction); 28 | 29 | return builder.AddConfigurationStore(); 30 | } 31 | 32 | public static IIdentityServerBuilder AddConfigurationStore( 33 | this IIdentityServerBuilder builder, IConfiguration configuration) 34 | { 35 | builder.Services.Configure(configuration); 36 | 37 | return builder.AddConfigurationStore(); 38 | } 39 | 40 | public static IIdentityServerBuilder AddOperationalStore( 41 | this IIdentityServerBuilder builder, 42 | Action setupAction, 43 | Action tokenCleanUpOptions = null) 44 | { 45 | builder.Services.Configure(setupAction); 46 | 47 | return builder.AddOperationalStore(tokenCleanUpOptions); 48 | } 49 | 50 | public static IIdentityServerBuilder AddOperationalStore( 51 | this IIdentityServerBuilder builder, 52 | IConfiguration configuration, 53 | Action tokenCleanUpOptions = null) 54 | { 55 | builder.Services.Configure(configuration); 56 | 57 | return builder.AddOperationalStore(tokenCleanUpOptions); 58 | } 59 | 60 | private static IIdentityServerBuilder AddConfigurationStore( 61 | this IIdentityServerBuilder builder) 62 | { 63 | ConfigureIgnoreExtraElementsConfigurationStore(); 64 | 65 | builder.Services.AddScoped(); 66 | 67 | builder.Services.AddTransient(); 68 | builder.Services.AddTransient(); 69 | builder.Services.AddTransient(); 70 | 71 | return builder; 72 | } 73 | 74 | private static IIdentityServerBuilder AddOperationalStore( 75 | this IIdentityServerBuilder builder, 76 | Action tokenCleanUpOptions = null) 77 | { 78 | ConfigureIgnoreExtraElementsOperationalStore(); 79 | 80 | builder.Services.AddScoped(); 81 | 82 | builder.Services.AddTransient(); 83 | 84 | var tco = new TokenCleanupOptions(); 85 | tokenCleanUpOptions?.Invoke(tco); 86 | builder.Services.AddSingleton(tco); 87 | builder.Services.AddTransient(); 88 | builder.Services.AddSingleton(); 89 | 90 | return builder; 91 | } 92 | 93 | private static void ConfigureIgnoreExtraElementsConfigurationStore() 94 | { 95 | BsonClassMap.RegisterClassMap(cm => 96 | { 97 | cm.AutoMap(); 98 | cm.SetIgnoreExtraElements(true); 99 | }); 100 | BsonClassMap.RegisterClassMap(cm => 101 | { 102 | cm.AutoMap(); 103 | cm.SetIgnoreExtraElements(true); 104 | }); 105 | BsonClassMap.RegisterClassMap(cm => 106 | { 107 | cm.AutoMap(); 108 | cm.SetIgnoreExtraElements(true); 109 | }); 110 | BsonClassMap.RegisterClassMap(cm => 111 | { 112 | cm.AutoMap(); 113 | cm.SetIgnoreExtraElements(true); 114 | }); 115 | } 116 | 117 | private static void ConfigureIgnoreExtraElementsOperationalStore() 118 | { 119 | BsonClassMap.RegisterClassMap(cm => 120 | { 121 | cm.AutoMap(); 122 | cm.SetIgnoreExtraElements(true); 123 | }); 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/IdentityServer4.MongoDB.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | MongoDB persistence layer for IdentityServer4 5 | 4.0.0-rc.2 6 | Diogo Damiani 7 | netcoreapp3.1 8 | IdentityServer4.Contrib.MongoDB 9 | IdentityServer4.Contrib.MongoDB 10 | OAuth2;OAuth 2.0;OpenID Connect;Security;Identity;IdentityServer;MongoDB 11 | https://identityserver.github.io/Documentation/assets/images/icons/IDserver_icon128.jpg 12 | https://github.com/diogodamiani/IdentityServer4.MongoDB 13 | https://github.com/diogodamiani/IdentityServer4.MongoDB/LICENSE 14 | false 15 | false 16 | false 17 | False 18 | true 19 | 4.0.0-rc.2 20 | Migration to .NET Core 3.1 and IdentityServer 4.0 21 | Fix #31, #34 22 | Token cleanup enhancement 23 | true 24 | 4.0.0.1 25 | 4.0.0.1 26 | false 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Interfaces/IConfigurationDbContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.MongoDB.Entities; 6 | using System; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace IdentityServer4.MongoDB.Interfaces 11 | { 12 | public interface IConfigurationDbContext : IDisposable 13 | { 14 | IQueryable Clients { get; } 15 | IQueryable IdentityResources { get; } 16 | IQueryable ApiResources { get; } 17 | IQueryable ApiScopes { get; } 18 | 19 | Task AddClient(Client entity); 20 | 21 | Task AddIdentityResource(IdentityResource entity); 22 | 23 | Task AddApiResource(ApiResource entity); 24 | 25 | Task AddApiScope(ApiScope entity); 26 | } 27 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Interfaces/IPersistedGrantDbContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | using IdentityServer4.MongoDB.Entities; 5 | using System; 6 | using System.Linq; 7 | using System.Linq.Expressions; 8 | using System.Threading.Tasks; 9 | 10 | namespace IdentityServer4.MongoDB.Interfaces 11 | { 12 | public interface IPersistedGrantDbContext : IDisposable 13 | { 14 | IQueryable PersistedGrants { get; } 15 | 16 | Task Remove(Expression> filter); 17 | 18 | Task RemoveExpired(); 19 | 20 | Task InsertOrUpdate(Expression> filter, PersistedGrant entity); 21 | } 22 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Mappers/ApiResourceMapperProfile.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | using AutoMapper; 5 | 6 | using IdentityServer4.MongoDB.Entities; 7 | 8 | using System.Linq; 9 | 10 | namespace IdentityServer4.MongoDB.Mappers 11 | { 12 | /// 13 | /// AutoMapper configuration for API resource 14 | /// Between model and entity 15 | /// 16 | public class ApiResourceMapperProfile : Profile 17 | { 18 | /// 19 | /// 20 | /// 21 | public ApiResourceMapperProfile() 22 | { 23 | CreateMap().ConvertUsing(uc => uc.Type); 24 | 25 | // entity to model 26 | CreateMap(MemberList.Destination) 27 | .ForMember(x => x.Properties, 28 | opt => opt.MapFrom(src => src.Properties.ToDictionary(item => item.Key, item => item.Value))) 29 | .ForMember(x => x.ApiSecrets, opt => opt.MapFrom(src => src.Secrets.Select(x => x))) 30 | .ForMember(x => x.Scopes, opt => opt.MapFrom(src => src.Scopes.Select(x => x))) 31 | .ForMember(x => x.UserClaims, opts => opts.MapFrom(src => src.UserClaims.Select(x => x.Type))); 32 | CreateMap(MemberList.Destination); 33 | CreateMap(MemberList.Destination) 34 | .ForMember(x => x.UserClaims, opt => opt.MapFrom(src => src.UserClaims)); 35 | 36 | // model to entity 37 | CreateMap(MemberList.Source) 38 | .ForMember(x => x.Properties, 39 | opt => opt.MapFrom(src => src.Properties.ToDictionary(item => item.Key, item => item.Value))) 40 | .ForMember(x => x.Secrets, opts => opts.MapFrom(src => src.ApiSecrets.Select(x => x))) 41 | .ForMember(x => x.Scopes, opts => opts.MapFrom(src => src.Scopes.Select(x => x))) 42 | .ForMember(x => x.UserClaims, opts => opts.MapFrom(src => src.UserClaims.Select(x => new ApiResourceClaim { Type = x }))); 43 | CreateMap(MemberList.Source); 44 | CreateMap(MemberList.Source) 45 | .ForMember(x => x.UserClaims, opts => opts.MapFrom(src => src.UserClaims.Select(x => new ApiScopeClaim { Type = x }))); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Mappers/ApiResourceMappers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using AutoMapper; 6 | using IdentityServer4.MongoDB.Entities; 7 | 8 | namespace IdentityServer4.MongoDB.Mappers 9 | { 10 | public static class ApiResourceMappers 11 | { 12 | static ApiResourceMappers() 13 | { 14 | Mapper = new MapperConfiguration(cfg => cfg.AddProfile()) 15 | .CreateMapper(); 16 | } 17 | 18 | internal static IMapper Mapper { get; } 19 | 20 | public static Models.ApiResource ToModel(this ApiResource resource) 21 | { 22 | return resource == null ? null : Mapper.Map(resource); 23 | } 24 | 25 | public static ApiResource ToEntity(this Models.ApiResource resource) 26 | { 27 | return resource == null ? null : Mapper.Map(resource); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Mappers/ApiScopeMapperProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using IdentityServer4.MongoDB.Entities; 3 | using System.Linq; 4 | 5 | namespace IdentityServer4.MongoDB.Mappers 6 | { 7 | public class ApiScopeMapperProfile : Profile 8 | { 9 | /// 10 | /// 11 | /// 12 | public ApiScopeMapperProfile() 13 | { 14 | CreateMap().ConvertUsing(uc => uc.Type); 15 | 16 | // entity to model 17 | CreateMap(MemberList.Destination) 18 | .ForMember(x => x.UserClaims, opt => opt.MapFrom(src => src.UserClaims)); 19 | 20 | // model to entity 21 | CreateMap(MemberList.Source) 22 | .ForMember(x => x.UserClaims, opts => opts.MapFrom(src => src.UserClaims.Select(x => new ApiScopeClaim { Type = x }))); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Mappers/ApiScopeMappers.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using IdentityServer4.MongoDB.Entities; 3 | 4 | namespace IdentityServer4.MongoDB.Mappers 5 | { 6 | public static class ApiScopeMappers 7 | { 8 | static ApiScopeMappers() 9 | { 10 | Mapper = new MapperConfiguration(cfg => cfg.AddProfile()) 11 | .CreateMapper(); 12 | } 13 | 14 | internal static IMapper Mapper { get; } 15 | 16 | public static Models.ApiScope ToModel(this ApiScope scope) 17 | { 18 | return scope == null ? null : Mapper.Map(scope); 19 | } 20 | 21 | public static ApiScope ToEntity(this Models.ApiScope scope) 22 | { 23 | return scope == null ? null : Mapper.Map(scope); 24 | } 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Mappers/ClientMapperProfile.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using AutoMapper; 6 | using IdentityServer4.MongoDB.Entities; 7 | using System.Linq; 8 | using System.Security.Claims; 9 | 10 | namespace IdentityServer4.MongoDB.Mappers 11 | { 12 | /// 13 | /// AutoMapper configuration for Client 14 | /// Between model and entity 15 | /// 16 | public class ClientMapperProfile : Profile 17 | { 18 | /// 19 | /// 20 | /// {ClientMapperProfile} 21 | /// 22 | /// 23 | public ClientMapperProfile() 24 | { 25 | // entity to model 26 | CreateMap(MemberList.Destination) 27 | .ForMember(x => x.Properties, 28 | opt => opt.MapFrom(src => src.Properties.ToDictionary(item => item.Key, item => item.Value))) 29 | .ForMember(x => x.AllowedGrantTypes, 30 | opt => opt.MapFrom(src => src.AllowedGrantTypes.Select(x => x.GrantType))) 31 | .ForMember(x => x.RedirectUris, opt => opt.MapFrom(src => src.RedirectUris.Select(x => x.RedirectUri))) 32 | .ForMember(x => x.PostLogoutRedirectUris, 33 | opt => opt.MapFrom(src => src.PostLogoutRedirectUris.Select(x => x.PostLogoutRedirectUri))) 34 | .ForMember(x => x.AllowedScopes, opt => opt.MapFrom(src => src.AllowedScopes.Select(x => x.Scope))) 35 | .ForMember(x => x.ClientSecrets, opt => opt.MapFrom(src => src.ClientSecrets.Select(x => x))) 36 | .ForMember(x => x.Claims, opt => opt.MapFrom(src => src.Claims.Select(x => new Models.ClientClaim(x.Type, x.Value)))) 37 | .ForMember(x => x.IdentityProviderRestrictions, 38 | opt => opt.MapFrom(src => src.IdentityProviderRestrictions.Select(x => x.Provider))) 39 | .ForMember(x => x.AllowedCorsOrigins, 40 | opt => opt.MapFrom(src => src.AllowedCorsOrigins.Select(x => x.Origin))); 41 | 42 | CreateMap(MemberList.Destination) 43 | .ForMember(dest => dest.Type, opt => opt.Condition(srs => srs != null)); 44 | 45 | // model to entity 46 | CreateMap(MemberList.Source) 47 | .ForMember(x => x.Properties, 48 | opt => opt.MapFrom(src => src.Properties.ToList().Select(x => new ClientProperty { Key = x.Key, Value = x.Value }))) 49 | .ForMember(x => x.AllowedGrantTypes, 50 | opt => opt.MapFrom(src => src.AllowedGrantTypes.Select(x => new ClientGrantType {GrantType = x}))) 51 | .ForMember(x => x.RedirectUris, 52 | opt => opt.MapFrom(src => src.RedirectUris.Select(x => new ClientRedirectUri {RedirectUri = x}))) 53 | .ForMember(x => x.PostLogoutRedirectUris, 54 | opt => 55 | opt.MapFrom( 56 | src => 57 | src.PostLogoutRedirectUris.Select( 58 | x => new ClientPostLogoutRedirectUri {PostLogoutRedirectUri = x}))) 59 | .ForMember(x => x.AllowedScopes, 60 | opt => opt.MapFrom(src => src.AllowedScopes.Select(x => new ClientScope {Scope = x}))) 61 | .ForMember(x => x.Claims, 62 | opt => opt.MapFrom(src => src.Claims.Select(x => new ClientClaim {Type = x.Type, Value = x.Value}))) 63 | .ForMember(x => x.IdentityProviderRestrictions, 64 | opt => 65 | opt.MapFrom( 66 | src => src.IdentityProviderRestrictions.Select(x => new ClientIdPRestriction {Provider = x}))) 67 | .ForMember(x => x.AllowedCorsOrigins, 68 | opt => opt.MapFrom(src => src.AllowedCorsOrigins.Select(x => new ClientCorsOrigin {Origin = x}))); 69 | CreateMap(MemberList.Source); 70 | 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Mappers/ClientMappers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using AutoMapper; 6 | 7 | namespace IdentityServer4.MongoDB.Mappers 8 | { 9 | /// 10 | /// Extension methods to map to/from entity/model for clients. 11 | /// 12 | public static class ClientMappers 13 | { 14 | static ClientMappers() 15 | { 16 | Mapper = new MapperConfiguration(cfg => cfg.AddProfile()) 17 | .CreateMapper(); 18 | } 19 | 20 | internal static IMapper Mapper { get; } 21 | 22 | /// 23 | /// Maps an entity to a model. 24 | /// 25 | /// The entity. 26 | /// 27 | public static Models.Client ToModel(this Entities.Client entity) 28 | { 29 | return Mapper.Map(entity); 30 | } 31 | 32 | /// 33 | /// Maps a model to an entity. 34 | /// 35 | /// The model. 36 | /// 37 | public static Entities.Client ToEntity(this Models.Client model) 38 | { 39 | return Mapper.Map(model); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Mappers/IdentityResourceMapperProfile.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | using AutoMapper; 5 | using IdentityServer4.MongoDB.Entities; 6 | using System.Linq; 7 | 8 | namespace IdentityServer4.MongoDB.Mappers 9 | { 10 | /// 11 | /// AutoMapper configuration for identity resource 12 | /// Between model and entity 13 | /// 14 | public class IdentityResourceMapperProfile : Profile 15 | { 16 | /// 17 | /// 18 | /// 19 | public IdentityResourceMapperProfile() 20 | { 21 | CreateMap().ConvertUsing(uc => uc.Type); 22 | 23 | // entity to model 24 | CreateMap(MemberList.Destination) 25 | .ForMember(x => x.Properties, 26 | opt => opt.MapFrom(src => src.Properties.ToDictionary(item => item.Key, item => item.Value))) 27 | .ForMember(x => x.UserClaims, opt => opt.MapFrom(src => src.UserClaims)); 28 | 29 | // model to entity 30 | CreateMap(MemberList.Source) 31 | .ForMember(x => x.Properties, 32 | opt => opt.MapFrom(src => src.Properties.ToDictionary(item => item.Key, item => item.Value))) 33 | .ForMember(x => x.UserClaims, opts => opts.MapFrom(src => src.UserClaims.Select(x => new IdentityClaim { Type = x }))); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Mappers/IdentityResourceMappers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using AutoMapper; 6 | using IdentityServer4.MongoDB.Entities; 7 | 8 | namespace IdentityServer4.MongoDB.Mappers 9 | { 10 | public static class IdentityResourceMappers 11 | { 12 | static IdentityResourceMappers() 13 | { 14 | Mapper = new MapperConfiguration(cfg => cfg.AddProfile()) 15 | .CreateMapper(); 16 | } 17 | 18 | internal static IMapper Mapper { get; } 19 | 20 | public static Models.IdentityResource ToModel(this IdentityResource resource) 21 | { 22 | return resource == null ? null : Mapper.Map(resource); 23 | } 24 | 25 | public static IdentityResource ToEntity(this Models.IdentityResource resource) 26 | { 27 | return resource == null ? null : Mapper.Map(resource); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Mappers/PersistedGrantMapperProfile.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using AutoMapper; 6 | 7 | namespace IdentityServer4.MongoDB.Mappers 8 | { 9 | /// 10 | /// AutoMapper Config for PersistedGrant 11 | /// Between Model and Entity 12 | /// 13 | /// 14 | /// 15 | public class PersistedGrantMapperProfile : Profile 16 | { 17 | /// 18 | /// 19 | /// 20 | /// 21 | public PersistedGrantMapperProfile() 22 | { 23 | // entity to model 24 | CreateMap(MemberList.Destination); 25 | 26 | // model to entity 27 | CreateMap(MemberList.Source); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Mappers/PersistedGrantMappers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using AutoMapper; 6 | using IdentityServer4.Models; 7 | 8 | namespace IdentityServer4.MongoDB.Mappers 9 | { 10 | public static class PersistedGrantMappers 11 | { 12 | static PersistedGrantMappers() 13 | { 14 | Mapper = new MapperConfiguration(cfg =>cfg.AddProfile()) 15 | .CreateMapper(); 16 | } 17 | 18 | internal static IMapper Mapper { get; } 19 | 20 | public static PersistedGrant ToModel(this Entities.PersistedGrant token) 21 | { 22 | return token == null ? null : Mapper.Map(token); 23 | } 24 | 25 | public static Entities.PersistedGrant ToEntity(this PersistedGrant token) 26 | { 27 | return token == null ? null : Mapper.Map(token); 28 | } 29 | 30 | public static void UpdateEntity(this PersistedGrant token, Entities.PersistedGrant target) 31 | { 32 | Mapper.Map(token, target); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Options/TokenCleanupOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IdentityServer4.MongoDB.Options 6 | { 7 | public class TokenCleanupOptions 8 | { 9 | public int Interval { get; set; } = 60; 10 | public bool Enable { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("IdentityServer4.Contrib.MongoDB")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("9511145a-8dad-446c-9d15-a4952dcb9ba5")] 20 | -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Services/CorsPolicyService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.MongoDB.Interfaces; 6 | using IdentityServer4.Services; 7 | using Microsoft.Extensions.Logging; 8 | using System; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | 12 | namespace IdentityServer4.MongoDB.Services 13 | { 14 | public class CorsPolicyService : ICorsPolicyService 15 | { 16 | private readonly IConfigurationDbContext _context; 17 | private readonly ILogger _logger; 18 | 19 | 20 | public CorsPolicyService(IConfigurationDbContext context, ILogger logger) 21 | { 22 | _context = context ?? throw new ArgumentNullException(nameof(context)); 23 | _logger = logger; 24 | } 25 | 26 | public Task IsOriginAllowedAsync(string origin) 27 | { 28 | // If we use SelectMany directly, we got a NotSupportedException inside MongoDB driver. 29 | // Details: 30 | // System.NotSupportedException: Unable to determine the serialization information for the collection 31 | // selector in the tree: aggregate([]).SelectMany(x => x.AllowedCorsOrigins.Select(y => y.Origin)) 32 | var origins = _context.Clients.Select(x => x.AllowedCorsOrigins.Select(y => y.Origin)).ToList(); 33 | 34 | // As a workaround, we use SelectMany in memory. 35 | var distinctOrigins = origins.SelectMany(o => o).Where(x => x != null).Distinct(); 36 | 37 | var isAllowed = distinctOrigins.Contains(origin, StringComparer.OrdinalIgnoreCase); 38 | 39 | _logger.LogDebug("Origin {origin} is allowed: {originAllowed}", origin, isAllowed); 40 | 41 | return Task.FromResult(isAllowed); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Stores/ClientStore.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | using IdentityServer4.Models; 5 | using IdentityServer4.MongoDB.Interfaces; 6 | using IdentityServer4.MongoDB.Mappers; 7 | using IdentityServer4.Stores; 8 | using Microsoft.Extensions.Logging; 9 | using System; 10 | using System.Linq; 11 | using System.Threading.Tasks; 12 | 13 | namespace IdentityServer4.MongoDB.Stores 14 | { 15 | public class ClientStore : IClientStore 16 | { 17 | private readonly IConfigurationDbContext _context; 18 | private readonly ILogger _logger; 19 | 20 | public ClientStore(IConfigurationDbContext context, ILogger logger) 21 | { 22 | _context = context ?? throw new ArgumentNullException(nameof(context)); 23 | _logger = logger; 24 | } 25 | 26 | public Task FindClientByIdAsync(string clientId) 27 | { 28 | var client = _context.Clients.FirstOrDefault(x => x.ClientId == clientId); 29 | 30 | var model = client?.ToModel(); 31 | 32 | _logger.LogDebug("{clientId} found in database: {clientIdFound}", clientId, model != null); 33 | 34 | return Task.FromResult(model); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Stores/PersistedGrantStore.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.Models; 6 | using IdentityServer4.MongoDB.Interfaces; 7 | using IdentityServer4.MongoDB.Mappers; 8 | using IdentityServer4.Stores; 9 | using Microsoft.Extensions.Logging; 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Linq; 13 | using System.Threading.Tasks; 14 | 15 | namespace IdentityServer4.MongoDB.Stores 16 | { 17 | public class PersistedGrantStore : IPersistedGrantStore 18 | { 19 | private readonly IPersistedGrantDbContext _context; 20 | private readonly ILogger _logger; 21 | 22 | public PersistedGrantStore(IPersistedGrantDbContext context, ILogger logger) 23 | { 24 | _context = context; 25 | _logger = logger; 26 | } 27 | 28 | public async Task StoreAsync(PersistedGrant token) 29 | { 30 | try 31 | { 32 | _logger.LogDebug("Try to save or update {persistedGrantKey} in database", token.Key); 33 | await _context.InsertOrUpdate(t => t.Key == token.Key, token.ToEntity()); 34 | _logger.LogDebug("{persistedGrantKey} stored in database", token.Key); 35 | } 36 | catch (Exception ex) 37 | { 38 | _logger.LogError(0, ex, "Exception storing persisted grant"); 39 | throw; 40 | } 41 | } 42 | 43 | public Task GetAsync(string key) 44 | { 45 | var persistedGrant = _context.PersistedGrants.FirstOrDefault(x => x.Key == key); 46 | var model = persistedGrant.ToModel(); 47 | 48 | _logger.LogDebug("{persistedGrantKey} found in database: {persistedGrantKeyFound}", key, model != null); 49 | 50 | return Task.FromResult(model); 51 | } 52 | 53 | public Task> GetAllAsync(PersistedGrantFilter filter) 54 | { 55 | Validate(filter); 56 | 57 | var persistedGrants = _context.PersistedGrants.Where( 58 | x => (string.IsNullOrWhiteSpace(filter.SubjectId) || x.SubjectId == filter.SubjectId) && 59 | (string.IsNullOrWhiteSpace(filter.ClientId) || x.ClientId == filter.ClientId) && 60 | (string.IsNullOrWhiteSpace(filter.Type) || x.Type == filter.Type)).ToList(); 61 | 62 | var model = persistedGrants.Select(x => x.ToModel()); 63 | 64 | _logger.LogDebug($"{persistedGrants.Count} persisted grants found for filter: {filter}"); 65 | 66 | return Task.FromResult(model); 67 | } 68 | 69 | public Task RemoveAsync(string key) 70 | { 71 | _logger.LogDebug("removing {persistedGrantKey} persisted grant from database", key); 72 | 73 | _context.Remove(x => x.Key == key); 74 | 75 | return Task.FromResult(0); 76 | } 77 | 78 | public Task RemoveAllAsync(PersistedGrantFilter filter) 79 | { 80 | Validate(filter); 81 | 82 | _logger.LogDebug($"removing persisted grants from database for filter: {filter}"); 83 | 84 | _context.Remove( 85 | x => (string.IsNullOrWhiteSpace(filter.SubjectId) || x.SubjectId == filter.SubjectId) && 86 | (string.IsNullOrWhiteSpace(filter.ClientId) || x.ClientId == filter.ClientId) && 87 | (string.IsNullOrWhiteSpace(filter.Type) || x.Type == filter.Type)); 88 | 89 | return Task.FromResult(0); 90 | } 91 | 92 | private void Validate(PersistedGrantFilter filter) 93 | { 94 | if (filter == null) throw new ArgumentNullException(nameof(filter)); 95 | 96 | if (string.IsNullOrWhiteSpace(filter.ClientId) && 97 | string.IsNullOrWhiteSpace(filter.SubjectId) && 98 | string.IsNullOrWhiteSpace(filter.Type)) 99 | { 100 | throw new ArgumentException("No filter values set.", nameof(filter)); 101 | } 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/Stores/ResourceStore.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | using IdentityServer4.Models; 5 | using IdentityServer4.MongoDB.Interfaces; 6 | using IdentityServer4.MongoDB.Mappers; 7 | using IdentityServer4.Stores; 8 | using Microsoft.Extensions.Logging; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using System.Threading.Tasks; 13 | 14 | namespace IdentityServer4.MongoDB.Stores 15 | { 16 | public class ResourceStore : IResourceStore 17 | { 18 | private readonly IConfigurationDbContext _context; 19 | private readonly ILogger _logger; 20 | 21 | public ResourceStore(IConfigurationDbContext context, ILogger logger) 22 | { 23 | _context = context ?? throw new ArgumentNullException(nameof(context)); 24 | _logger = logger; 25 | } 26 | 27 | public Task> FindApiResourcesByNameAsync(IEnumerable apiResourceNames) 28 | { 29 | var names = apiResourceNames.ToArray(); 30 | var apis = _context.ApiResources.Where(x => names.Contains(x.Name)); 31 | 32 | var results = apis.ToArray(); 33 | var models = results.Select(x => x.ToModel()).ToArray(); 34 | 35 | _logger.LogDebug("Found {scopes} API resources in database", models.Select(x => x.Scopes)); 36 | 37 | return Task.FromResult(models.AsEnumerable()); 38 | } 39 | 40 | public Task> FindApiScopesByNameAsync(IEnumerable scopeNames) 41 | { 42 | var names = scopeNames.ToArray(); 43 | var models = _context.ApiScopes 44 | .Where(x => names.Contains(x.Name)) 45 | .ToArray() 46 | .Select(x => x.ToModel()) 47 | .ToArray(); 48 | 49 | _logger.LogDebug("Found {scopes} API scopes in database", models.Select(x => x.Name)); 50 | 51 | return Task.FromResult(models.AsEnumerable()); 52 | 53 | } 54 | 55 | public Task> FindApiResourcesByScopeNameAsync(IEnumerable scopeNames) 56 | { 57 | var names = scopeNames.ToArray(); 58 | 59 | var apis = _context.ApiResources.Where(x => x.Scopes.Any(y => names.Contains(y))); 60 | 61 | var results = apis.ToArray(); 62 | var models = results.Select(x => x.ToModel()).ToArray(); 63 | 64 | _logger.LogDebug("Found {scopes} API resources in database", models.Select(x => x.Scopes)); 65 | 66 | return Task.FromResult(models.AsEnumerable()); 67 | } 68 | 69 | public Task> FindIdentityResourcesByScopeNameAsync(IEnumerable scopeNames) 70 | { 71 | var scopes = scopeNames.ToArray(); 72 | 73 | var resources = _context.IdentityResources.Where(x => scopes.Contains(x.Name)); 74 | 75 | var results = resources.ToArray(); 76 | 77 | _logger.LogDebug("Found {scopes} identity scopes in database", results.Select(x => x.Name)); 78 | 79 | return Task.FromResult(results.Select(x => x.ToModel()).ToArray().AsEnumerable()); 80 | } 81 | 82 | public Task GetAllResourcesAsync() 83 | { 84 | var identity = _context.IdentityResources; 85 | 86 | var apis = _context.ApiResources; 87 | 88 | var scopes = _context.ApiScopes; 89 | 90 | var result = new Resources( 91 | identity.ToArray().Select(x => x.ToModel()).AsEnumerable(), 92 | apis.ToArray().Select(x => x.ToModel()).AsEnumerable(), 93 | scopes.ToArray().Select(x => x.ToModel()).AsEnumerable()); 94 | 95 | _logger.LogDebug("Found {scopes} as all scopes in database", result.IdentityResources.Select(x => x.Name).Union(result.ApiResources.SelectMany(x => x.Scopes))); 96 | 97 | return Task.FromResult(result); 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/TokenCleanup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | using IdentityServer4.MongoDB.Interfaces; 5 | using Microsoft.Extensions.Logging; 6 | using System; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace IdentityServer4.MongoDB 11 | { 12 | public class TokenCleanup 13 | { 14 | private readonly IPersistedGrantDbContext _persistedGrantDbContext; 15 | private readonly ILogger _logger; 16 | 17 | public TokenCleanup(IPersistedGrantDbContext persistedGrantDbContext, ILogger logger) 18 | { 19 | _persistedGrantDbContext = persistedGrantDbContext; 20 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 21 | } 22 | 23 | /// 24 | /// Method to clear expired persisted grants. 25 | /// 26 | /// 27 | public async Task RemoveExpiredGrantsAsync() 28 | { 29 | try 30 | { 31 | _logger.LogTrace("Querying for expired grants to remove"); 32 | 33 | await RemoveGrantsAsync(); 34 | //TODO: await RemoveDeviceCodesAsync(); 35 | } 36 | catch (Exception ex) 37 | { 38 | _logger.LogError("Exception removing expired grants: {exception}", ex.Message); 39 | } 40 | } 41 | 42 | /// 43 | /// Removes the stale persisted grants. 44 | /// 45 | /// 46 | protected virtual async Task RemoveGrantsAsync() 47 | { 48 | var expired = _persistedGrantDbContext.PersistedGrants 49 | .Where(x => x.Expiration < DateTime.UtcNow) 50 | .ToArray(); 51 | 52 | var found = expired.Length; 53 | _logger.LogDebug("Removing {grantCount} grants", found); 54 | 55 | if (found > 0) 56 | { 57 | await _persistedGrantDbContext.RemoveExpired(); 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/IdentityServer4.MongoDB/TokenCleanupService.cs: -------------------------------------------------------------------------------- 1 | using IdentityServer4.MongoDB.Options; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using Microsoft.Extensions.Logging; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace IdentityServer4.MongoDB 12 | { 13 | /// 14 | /// Helper to cleanup expired persisted grants. 15 | /// 16 | public class TokenCleanupService : BackgroundService 17 | { 18 | private readonly IServiceProvider _serviceProvider; 19 | private readonly TokenCleanupOptions _options; 20 | private readonly ILogger _logger; 21 | 22 | public TokenCleanupService(IServiceProvider serviceProvider, TokenCleanupOptions options, ILogger logger) 23 | { 24 | _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); 25 | _options = options ?? throw new ArgumentNullException(nameof(options)); 26 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 27 | } 28 | 29 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 30 | { 31 | await Task.Yield(); 32 | 33 | if (!_options.Enable) 34 | { 35 | _logger.LogDebug("Grant removal is not enabled"); 36 | return; 37 | } 38 | 39 | if (_options.Interval < 1) 40 | { 41 | _logger.LogDebug("Grant removal interval must be more than 1 second"); 42 | return; 43 | } 44 | 45 | try 46 | { 47 | _logger.LogDebug("Grant removal started"); 48 | 49 | while (!stoppingToken.IsCancellationRequested) 50 | { 51 | await Task.Delay(_options.Interval * 1000, stoppingToken); // ms 52 | await RemoveExpiredGrantsAsync(); 53 | } 54 | } 55 | catch (TaskCanceledException) 56 | { 57 | } 58 | catch (Exception ex) 59 | { 60 | _logger.LogError("Error running grant removal: {exception}", ex.Message); 61 | } 62 | finally 63 | { 64 | _logger.LogDebug("Grant removal ended"); 65 | } 66 | } 67 | 68 | private async Task RemoveExpiredGrantsAsync() 69 | { 70 | using (var serviceScope = _serviceProvider.GetRequiredService().CreateScope()) 71 | { 72 | var tokenCleanup = serviceScope.ServiceProvider.GetRequiredService(); 73 | await tokenCleanup.RemoveExpiredGrantsAsync(); 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /test/IdentityServer4.Tests.MongoDB/IdentityServer4.Tests.MongoDB.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/IdentityServer4.Tests.MongoDB/Mappers/ApiResourceMappersTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using IdentityServer4.MongoDB.Entities; 3 | using IdentityServer4.MongoDB.Mappers; 4 | using Xunit; 5 | 6 | namespace IdentityServer4.Tests.MongoDB.Mappers 7 | { 8 | 9 | public class ApiResourceMappersTest 10 | { 11 | 12 | [Fact] 13 | public void ToEntity_UsingStringsAsScopes_MapsCorrectly() 14 | { 15 | var toBeMapped = new Models.ApiResource("test-resource", "Test Resource") { 16 | Scopes = {"test", "test.scope1", "test.scope2"} 17 | }; 18 | 19 | var mappedEntity = toBeMapped.ToEntity(); 20 | 21 | Assert.Collection( 22 | mappedEntity.Scopes, 23 | scope => Assert.Equal("test", scope), 24 | scope => Assert.Equal("test.scope1", scope), 25 | scope => Assert.Equal("test.scope2", scope) 26 | ); 27 | } 28 | 29 | [Fact] 30 | public void ToModel_UsingStringsAsScopes_MapsCorrectly() 31 | { 32 | var toBeMapped = new ApiResource { 33 | Name = "my-api", 34 | DisplayName = "My API", 35 | Scopes = new List { "api", "api.scope1", "api.scope2" } 36 | }; 37 | 38 | var mappedModel = toBeMapped.ToModel(); 39 | 40 | Assert.Collection( 41 | mappedModel.Scopes, 42 | scope => Assert.Equal("api", scope), 43 | scope => Assert.Equal("api.scope1", scope), 44 | scope => Assert.Equal("api.scope2", scope) 45 | ); 46 | } 47 | 48 | [Fact] 49 | public void ToModel_WithUserClaims_MapsCorrectly() 50 | { 51 | var toBeMapped = new ApiResource { 52 | Name = "my-api", 53 | DisplayName = "My API", 54 | Scopes = new List { "api", "api.scope1", "api.scope2" }, 55 | UserClaims = new List { 56 | new ApiResourceClaim { Id = 1, Type = "claim1" }, 57 | new ApiResourceClaim { Id = 2, Type = "claim2" } 58 | } 59 | }; 60 | 61 | var mappedModel = toBeMapped.ToModel(); 62 | 63 | Assert.Collection( 64 | mappedModel.UserClaims, 65 | claim => Assert.Equal("claim1", claim), 66 | claim => Assert.Equal("claim2", claim) 67 | ); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /test/IdentityServer4.Tests.MongoDB/Mappers/ApiScopeMappersTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using IdentityServer4.MongoDB.Entities; 3 | using IdentityServer4.MongoDB.Mappers; 4 | using Xunit; 5 | 6 | namespace IdentityServer4.Tests.MongoDB.Mappers 7 | { 8 | public class ApiScopeMappersTest 9 | { 10 | 11 | [Fact] 12 | public void ToModel_UserClaims_MapsCorrectly() 13 | { 14 | var toBeMapped = new ApiScope { 15 | Name = "aScope", 16 | UserClaims = new List { 17 | new ApiScopeClaim {Id = 1, Type = "claim1"}, 18 | new ApiScopeClaim {Id = 2, Type = "claim2"} 19 | } 20 | }; 21 | 22 | var mappedModel = toBeMapped.ToModel(); 23 | 24 | Assert.Collection( 25 | mappedModel.UserClaims, 26 | claim => Assert.Equal("claim1", claim), 27 | claim => Assert.Equal("claim2", claim) 28 | ); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /test/IdentityServer4.Tests.MongoDB/Mappers/ClientMappersTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using IdentityServer4.MongoDB.Entities; 3 | using IdentityServer4.MongoDB.Mappers; 4 | using Xunit; 5 | 6 | namespace IdentityServer4.Tests.MongoDB.Mappers 7 | { 8 | 9 | public class ClientMappersTest 10 | { 11 | 12 | [Fact] 13 | public void ToModel_ClientClaims_MapsCorrectly() 14 | { 15 | var toBeMapped = new Client { 16 | ClientId = "someId", 17 | AllowedScopes = new List { new ClientScope { Scope = "dsquad:public"} }, 18 | ClientSecrets = new List { new ClientSecret { Value = "secret" } }, 19 | AllowedGrantTypes = new List { new ClientGrantType {GrantType = "client_credentials"} }, 20 | AccessTokenType = 0, 21 | AccessTokenLifetime = int.MaxValue, 22 | Enabled = true, 23 | AlwaysSendClientClaims = true, 24 | ClientClaimsPrefix = "", 25 | Claims = new List { new ClientClaim { Type = "hello", Value = "world" } } 26 | }; 27 | 28 | var mappedClient = toBeMapped.ToModel(); 29 | 30 | var expectedClientClaim = new Models.ClientClaim { 31 | Type = "hello", 32 | Value = "world" 33 | }; 34 | 35 | Assert.Collection( 36 | mappedClient.Claims, 37 | claim => Assert.Equal(expectedClientClaim, claim) 38 | ); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /test/IdentityServer4.Tests.MongoDB/Mappers/IdentityResourceMappersTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using IdentityServer4.MongoDB.Entities; 3 | using IdentityServer4.MongoDB.Mappers; 4 | using Xunit; 5 | 6 | namespace IdentityServer4.Tests.MongoDB.Mappers 7 | { 8 | public class IdentityResourceMappersTest 9 | { 10 | 11 | [Fact] 12 | public void ToModel_UserClaimsAreMappedCorrectly() 13 | { 14 | var toBeMapped = new IdentityResource { 15 | Name = "profile", 16 | UserClaims = new List { 17 | new IdentityClaim {Id = 1, Type = "name"}, 18 | new IdentityClaim {Id = 2, Type = "gender"} 19 | } 20 | }; 21 | 22 | var mappedModel = toBeMapped.ToModel(); 23 | 24 | Assert.Collection( 25 | mappedModel.UserClaims, 26 | scope => Assert.Equal("name", scope), 27 | scope => Assert.Equal("gender", scope) 28 | ); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /tools/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | --------------------------------------------------------------------------------