From 85cd111f5f357fae734113ad35e3b2a19f90c457 Mon Sep 17 00:00:00 2001 From: Muhammad Azeez Date: Sat, 16 Jan 2021 21:20:05 +0300 Subject: [PATCH] add a sample AuthorizationServer to the repo and put traffic-police app in a separate folder --- .../OidcSamples.AuthorizationServer/Config.cs | 111 ++ .../OidcSamples.AuthorizationServer.csproj | 14 + .../Program.cs | 53 + .../Properties/launchSettings.json | 12 + .../Quickstart/Account/AccountController.cs | 368 ++++++ .../Quickstart/Account/AccountOptions.cs | 20 + .../Quickstart/Account/ExternalController.cs | 196 +++ .../Quickstart/Account/ExternalProvider.cs | 12 + .../Quickstart/Account/LoggedOutViewModel.cs | 19 + .../Quickstart/Account/LoginInputModel.cs | 18 + .../Quickstart/Account/LoginViewModel.cs | 22 + .../Quickstart/Account/LogoutInputModel.cs | 11 + .../Quickstart/Account/LogoutViewModel.cs | 11 + .../Quickstart/Account/RedirectViewModel.cs | 12 + .../Quickstart/Consent/ConsentController.cs | 262 ++++ .../Quickstart/Consent/ConsentInputModel.cs | 17 + .../Quickstart/Consent/ConsentOptions.cs | 16 + .../Quickstart/Consent/ConsentViewModel.cs | 19 + .../Consent/ProcessConsentResult.cs | 21 + .../Quickstart/Consent/ScopeViewModel.cs | 16 + .../Device/DeviceAuthorizationInputModel.cs | 11 + .../Device/DeviceAuthorizationViewModel.cs | 12 + .../Quickstart/Device/DeviceController.cs | 232 ++++ .../Diagnostics/DiagnosticsController.cs | 29 + .../Diagnostics/DiagnosticsViewModel.cs | 32 + .../Quickstart/Extensions.cs | 27 + .../Quickstart/Grants/GrantsController.cs | 97 ++ .../Quickstart/Grants/GrantsViewModel.cs | 27 + .../Quickstart/Home/ErrorViewModel.cs | 22 + .../Quickstart/Home/HomeController.cs | 65 + .../Quickstart/SecurityHeadersAttribute.cs | 56 + .../Quickstart/TestUsers.cs | 38 + .../Startup.cs | 60 + .../Views/Account/AccessDenied.cshtml | 7 + .../Views/Account/LoggedOut.cshtml | 34 + .../Views/Account/Login.cshtml | 87 ++ .../Views/Account/Logout.cshtml | 15 + .../Views/Consent/Index.cshtml | 104 ++ .../Views/Device/Success.cshtml | 7 + .../Views/Device/UserCodeCapture.cshtml | 23 + .../Views/Device/UserCodeConfirmation.cshtml | 108 ++ .../Views/Diagnostics/Index.cshtml | 64 + .../Views/Grants/Index.cshtml | 87 ++ .../Views/Home/Index.cshtml | 32 + .../Views/Shared/Error.cshtml | 40 + .../Views/Shared/Redirect.cshtml | 11 + .../Views/Shared/_Layout.cshtml | 28 + .../Views/Shared/_Nav.cshtml | 33 + .../Views/Shared/_ScopeListItem.cshtml | 34 + .../Views/Shared/_ValidationSummary.cshtml | 7 + .../Views/_ViewImports.cshtml | 2 + .../Views/_ViewStart.cshtml | 3 + .../tempkey.jwk | 1 + .../wwwroot/css/site.css | 24 + .../wwwroot/css/site.min.css | 1 + .../wwwroot/css/site.scss | 42 + .../wwwroot/favicon.ico | Bin 0 -> 1150 bytes .../wwwroot/icon.jpg | Bin 0 -> 19482 bytes .../wwwroot/icon.png | Bin 0 -> 20796 bytes .../wwwroot/js/signin-redirect.js | 1 + .../wwwroot/js/signout-redirect.js | 6 + .../wwwroot/lib/bootstrap/README.md | 209 +++ .../wwwroot/lib/bootstrap/scss/_alert.scss | 51 + .../wwwroot/lib/bootstrap/scss/_badge.scss | 54 + .../lib/bootstrap/scss/_breadcrumb.scss | 42 + .../lib/bootstrap/scss/_button-group.scss | 163 +++ .../wwwroot/lib/bootstrap/scss/_buttons.scss | 139 ++ .../wwwroot/lib/bootstrap/scss/_card.scss | 278 ++++ .../wwwroot/lib/bootstrap/scss/_carousel.scss | 197 +++ .../wwwroot/lib/bootstrap/scss/_close.scss | 41 + .../wwwroot/lib/bootstrap/scss/_code.scss | 48 + .../lib/bootstrap/scss/_custom-forms.scss | 521 ++++++++ .../wwwroot/lib/bootstrap/scss/_dropdown.scss | 191 +++ .../wwwroot/lib/bootstrap/scss/_forms.scss | 338 +++++ .../lib/bootstrap/scss/_functions.scss | 134 ++ .../wwwroot/lib/bootstrap/scss/_grid.scss | 69 + .../wwwroot/lib/bootstrap/scss/_images.scss | 42 + .../lib/bootstrap/scss/_input-group.scss | 191 +++ .../lib/bootstrap/scss/_jumbotron.scss | 17 + .../lib/bootstrap/scss/_list-group.scss | 158 +++ .../wwwroot/lib/bootstrap/scss/_media.scss | 8 + .../wwwroot/lib/bootstrap/scss/_mixins.scss | 47 + .../wwwroot/lib/bootstrap/scss/_modal.scss | 239 ++++ .../wwwroot/lib/bootstrap/scss/_nav.scss | 120 ++ .../wwwroot/lib/bootstrap/scss/_navbar.scss | 324 +++++ .../lib/bootstrap/scss/_pagination.scss | 73 ++ .../wwwroot/lib/bootstrap/scss/_popover.scss | 170 +++ .../wwwroot/lib/bootstrap/scss/_print.scss | 141 ++ .../wwwroot/lib/bootstrap/scss/_progress.scss | 46 + .../wwwroot/lib/bootstrap/scss/_reboot.scss | 482 +++++++ .../wwwroot/lib/bootstrap/scss/_root.scss | 20 + .../wwwroot/lib/bootstrap/scss/_spinners.scss | 55 + .../wwwroot/lib/bootstrap/scss/_tables.scss | 185 +++ .../wwwroot/lib/bootstrap/scss/_toasts.scss | 44 + .../wwwroot/lib/bootstrap/scss/_tooltip.scss | 115 ++ .../lib/bootstrap/scss/_transitions.scss | 20 + .../wwwroot/lib/bootstrap/scss/_type.scss | 125 ++ .../lib/bootstrap/scss/_utilities.scss | 17 + .../lib/bootstrap/scss/_variables.scss | 1143 +++++++++++++++++ .../lib/bootstrap/scss/bootstrap-grid.scss | 29 + .../lib/bootstrap/scss/bootstrap-reboot.scss | 12 + .../wwwroot/lib/bootstrap/scss/bootstrap.scss | 44 + .../lib/bootstrap/scss/mixins/_alert.scss | 13 + .../scss/mixins/_background-variant.scss | 22 + .../lib/bootstrap/scss/mixins/_badge.scss | 17 + .../bootstrap/scss/mixins/_border-radius.scss | 63 + .../bootstrap/scss/mixins/_box-shadow.scss | 20 + .../bootstrap/scss/mixins/_breakpoints.scss | 123 ++ .../lib/bootstrap/scss/mixins/_buttons.scss | 110 ++ .../lib/bootstrap/scss/mixins/_caret.scss | 62 + .../lib/bootstrap/scss/mixins/_clearfix.scss | 7 + .../lib/bootstrap/scss/mixins/_deprecate.scss | 10 + .../lib/bootstrap/scss/mixins/_float.scss | 14 + .../lib/bootstrap/scss/mixins/_forms.scss | 177 +++ .../lib/bootstrap/scss/mixins/_gradients.scss | 45 + .../scss/mixins/_grid-framework.scss | 71 + .../lib/bootstrap/scss/mixins/_grid.scss | 69 + .../lib/bootstrap/scss/mixins/_hover.scss | 37 + .../lib/bootstrap/scss/mixins/_image.scss | 36 + .../bootstrap/scss/mixins/_list-group.scss | 21 + .../lib/bootstrap/scss/mixins/_lists.scss | 7 + .../bootstrap/scss/mixins/_nav-divider.scss | 11 + .../bootstrap/scss/mixins/_pagination.scss | 22 + .../bootstrap/scss/mixins/_reset-text.scss | 17 + .../lib/bootstrap/scss/mixins/_resize.scss | 6 + .../bootstrap/scss/mixins/_screen-reader.scss | 34 + .../lib/bootstrap/scss/mixins/_size.scss | 7 + .../lib/bootstrap/scss/mixins/_table-row.scss | 39 + .../bootstrap/scss/mixins/_text-emphasis.scss | 17 + .../lib/bootstrap/scss/mixins/_text-hide.scss | 11 + .../bootstrap/scss/mixins/_text-truncate.scss | 8 + .../bootstrap/scss/mixins/_transition.scss | 16 + .../bootstrap/scss/mixins/_visibility.scss | 8 + .../lib/bootstrap/scss/utilities/_align.scss | 8 + .../bootstrap/scss/utilities/_background.scss | 19 + .../bootstrap/scss/utilities/_borders.scss | 75 ++ .../bootstrap/scss/utilities/_clearfix.scss | 3 + .../bootstrap/scss/utilities/_display.scss | 26 + .../lib/bootstrap/scss/utilities/_embed.scss | 39 + .../lib/bootstrap/scss/utilities/_flex.scss | 51 + .../lib/bootstrap/scss/utilities/_float.scss | 11 + .../bootstrap/scss/utilities/_overflow.scss | 5 + .../bootstrap/scss/utilities/_position.scss | 32 + .../scss/utilities/_screenreaders.scss | 11 + .../bootstrap/scss/utilities/_shadows.scss | 6 + .../lib/bootstrap/scss/utilities/_sizing.scss | 20 + .../bootstrap/scss/utilities/_spacing.scss | 73 ++ .../scss/utilities/_stretched-link.scss | 19 + .../lib/bootstrap/scss/utilities/_text.scss | 72 ++ .../bootstrap/scss/utilities/_visibility.scss | 13 + .../lib/bootstrap/scss/vendor/_rfs.scss | 204 +++ .../wwwroot/lib/jquery/LICENSE.txt | 20 + .../wwwroot/lib/jquery/README.md | 62 + .../OidcSamples/OidcSamples.TaxApp/Startup.cs | 10 +- .../OidcSamples.TrafficPoliceApi/Startup.cs | 2 +- CSharp/OidcSamples/OidcSamples.sln | 8 +- React/{ => traffic-police}/.gitignore | 0 React/{ => traffic-police}/README.md | 0 React/{ => traffic-police}/cert.pem | 0 React/{ => traffic-police}/key.pem | 0 React/{ => traffic-police}/keytmp.pem | 0 React/{ => traffic-police}/package-lock.json | 0 React/{ => traffic-police}/package.json | 0 React/{ => traffic-police}/public/favicon.ico | Bin React/{ => traffic-police}/public/index.html | 0 React/{ => traffic-police}/src/App.js | 14 +- .../src/actions/authActions.js | 0 .../{ => traffic-police}/src/actions/types.js | 0 .../{ => traffic-police}/src/images/logo.svg | 0 React/{ => traffic-police}/src/index.css | 0 React/{ => traffic-police}/src/index.js | 0 .../src/pages/RegisterVehicle.js | 0 .../src/pages/YourVehicles.js | 0 .../src/pages/components/Button.js | 0 .../src/pages/components/Card.js | 0 .../src/pages/components/Heading1.js | 0 .../src/pages/components/Heading3.js | 0 .../src/pages/components/LoginWindow.js | 0 .../src/pages/components/Main.js | 0 .../src/pages/components/Navbar.js | 0 .../pages/components/VehicleRegisterForm.js | 0 .../src/pages/components/VehiclesMain.js | 0 .../pages/components/inputs/InputSelect.js | 0 .../src/pages/components/inputs/InputText.js | 0 .../src/pages/components/table/ColumnName.js | 0 .../src/pages/components/table/EditBtns.js | 0 .../src/pages/components/table/Table.js | 0 .../src/pages/components/table/TableData.js | 0 React/{ => traffic-police}/src/pages/home.js | 0 React/{ => traffic-police}/src/pages/login.js | 0 .../src/pages/signin-oidc.js | 0 .../src/pages/signout-oidc.js | 0 .../src/reducers/authReducer.js | 0 .../src/reducers/index.js | 0 .../src/services/apiService.js | 0 .../src/services/userService.js | 4 +- React/{ => traffic-police}/src/store.js | 0 .../src/utils/authProvider.js | 0 .../src/utils/axiosHeaders.js | 0 .../src/utils/protectedRoute.js | 0 React/{ => traffic-police}/yarn.lock | 0 201 files changed, 10992 insertions(+), 16 deletions(-) create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Config.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/OidcSamples.AuthorizationServer.csproj create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Program.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Properties/launchSettings.json create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/AccountController.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/AccountOptions.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/ExternalController.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/ExternalProvider.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoggedOutViewModel.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoginInputModel.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoginViewModel.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LogoutInputModel.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LogoutViewModel.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/RedirectViewModel.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentController.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentInputModel.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentOptions.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentViewModel.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ProcessConsentResult.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ScopeViewModel.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceAuthorizationInputModel.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceAuthorizationViewModel.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceController.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Diagnostics/DiagnosticsController.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Diagnostics/DiagnosticsViewModel.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Extensions.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Grants/GrantsController.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Grants/GrantsViewModel.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Home/ErrorViewModel.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Home/HomeController.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/SecurityHeadersAttribute.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/TestUsers.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Startup.cs create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/AccessDenied.cshtml create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/LoggedOut.cshtml create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/Login.cshtml create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/Logout.cshtml create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Consent/Index.cshtml create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Device/Success.cshtml create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Device/UserCodeCapture.cshtml create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Device/UserCodeConfirmation.cshtml create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Diagnostics/Index.cshtml create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Grants/Index.cshtml create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Home/Index.cshtml create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/Error.cshtml create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/Redirect.cshtml create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_Layout.cshtml create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_Nav.cshtml create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_ScopeListItem.cshtml create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_ValidationSummary.cshtml create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/_ViewImports.cshtml create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/_ViewStart.cshtml create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/tempkey.jwk create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/css/site.css create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/css/site.min.css create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/css/site.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/favicon.ico create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/icon.jpg create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/icon.png create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/js/signin-redirect.js create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/js/signout-redirect.js create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/README.md create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_alert.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_badge.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_breadcrumb.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_button-group.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_buttons.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_card.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_carousel.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_close.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_code.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_custom-forms.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_dropdown.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_forms.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_functions.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_grid.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_images.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_input-group.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_jumbotron.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_list-group.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_media.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_mixins.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_modal.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_nav.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_navbar.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_pagination.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_popover.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_print.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_progress.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_reboot.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_root.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_spinners.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_tables.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_toasts.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_tooltip.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_transitions.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_type.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_utilities.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_variables.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/bootstrap-grid.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/bootstrap-reboot.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/bootstrap.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_alert.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_background-variant.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_badge.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_border-radius.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_box-shadow.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_breakpoints.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_buttons.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_caret.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_clearfix.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_deprecate.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_float.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_forms.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_gradients.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_grid-framework.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_grid.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_hover.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_image.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_list-group.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_lists.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_nav-divider.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_pagination.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_reset-text.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_resize.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_screen-reader.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_size.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_table-row.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_text-emphasis.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_text-hide.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_text-truncate.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_transition.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/mixins/_visibility.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_align.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_background.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_borders.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_clearfix.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_display.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_embed.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_flex.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_float.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_overflow.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_position.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_screenreaders.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_shadows.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_sizing.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_spacing.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_stretched-link.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_text.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/utilities/_visibility.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/vendor/_rfs.scss create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/jquery/LICENSE.txt create mode 100644 CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/jquery/README.md rename React/{ => traffic-police}/.gitignore (100%) rename React/{ => traffic-police}/README.md (100%) rename React/{ => traffic-police}/cert.pem (100%) rename React/{ => traffic-police}/key.pem (100%) rename React/{ => traffic-police}/keytmp.pem (100%) rename React/{ => traffic-police}/package-lock.json (100%) rename React/{ => traffic-police}/package.json (100%) rename React/{ => traffic-police}/public/favicon.ico (100%) rename React/{ => traffic-police}/public/index.html (100%) rename React/{ => traffic-police}/src/App.js (85%) rename React/{ => traffic-police}/src/actions/authActions.js (100%) rename React/{ => traffic-police}/src/actions/types.js (100%) rename React/{ => traffic-police}/src/images/logo.svg (100%) rename React/{ => traffic-police}/src/index.css (100%) rename React/{ => traffic-police}/src/index.js (100%) rename React/{ => traffic-police}/src/pages/RegisterVehicle.js (100%) rename React/{ => traffic-police}/src/pages/YourVehicles.js (100%) rename React/{ => traffic-police}/src/pages/components/Button.js (100%) rename React/{ => traffic-police}/src/pages/components/Card.js (100%) rename React/{ => traffic-police}/src/pages/components/Heading1.js (100%) rename React/{ => traffic-police}/src/pages/components/Heading3.js (100%) rename React/{ => traffic-police}/src/pages/components/LoginWindow.js (100%) rename React/{ => traffic-police}/src/pages/components/Main.js (100%) rename React/{ => traffic-police}/src/pages/components/Navbar.js (100%) rename React/{ => traffic-police}/src/pages/components/VehicleRegisterForm.js (100%) rename React/{ => traffic-police}/src/pages/components/VehiclesMain.js (100%) rename React/{ => traffic-police}/src/pages/components/inputs/InputSelect.js (100%) rename React/{ => traffic-police}/src/pages/components/inputs/InputText.js (100%) rename React/{ => traffic-police}/src/pages/components/table/ColumnName.js (100%) rename React/{ => traffic-police}/src/pages/components/table/EditBtns.js (100%) rename React/{ => traffic-police}/src/pages/components/table/Table.js (100%) rename React/{ => traffic-police}/src/pages/components/table/TableData.js (100%) rename React/{ => traffic-police}/src/pages/home.js (100%) rename React/{ => traffic-police}/src/pages/login.js (100%) rename React/{ => traffic-police}/src/pages/signin-oidc.js (100%) rename React/{ => traffic-police}/src/pages/signout-oidc.js (100%) rename React/{ => traffic-police}/src/reducers/authReducer.js (100%) rename React/{ => traffic-police}/src/reducers/index.js (100%) rename React/{ => traffic-police}/src/services/apiService.js (100%) rename React/{ => traffic-police}/src/services/userService.js (90%) rename React/{ => traffic-police}/src/store.js (100%) rename React/{ => traffic-police}/src/utils/authProvider.js (100%) rename React/{ => traffic-police}/src/utils/axiosHeaders.js (100%) rename React/{ => traffic-police}/src/utils/protectedRoute.js (100%) rename React/{ => traffic-police}/yarn.lock (100%) diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Config.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Config.cs new file mode 100644 index 0000000..81c38e2 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Config.cs @@ -0,0 +1,111 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using IdentityModel; +using IdentityServer4; +using IdentityServer4.Models; +using IdentityServer4.Stores; +using System.Collections.Generic; + +namespace OidcSamples.AuthorizationServer +{ + public static class Config + { + public static IEnumerable IdentityResources => + new IdentityResource[] + { + new IdentityResources.OpenId(), + new IdentityResources.Profile(), + new IdentityResources.Email(), + new IdentityResources.Address(), + }; + + private const string TrafficPoliceApi = "traffic-police-api"; + + public static IEnumerable ApiScopes => + new ApiScope[] + { + new ApiScope( + TrafficPoliceApi, + "Traffic Police API scope"), + }; + + public static IEnumerable ApiResources => + new ApiResource[] { + new ApiResource(TrafficPoliceApi, "Traffic Police API") + { + // This will make sure that `traffic-police-api` will be in the + // list of audiences when this scope is requested + Scopes = new List{ TrafficPoliceApi }, + }, + }; + + public static IEnumerable Clients => + new Client[] + { + new Client + { + // IdentityTokenLifetime = + // AuthorizationCodeLifetime = + AccessTokenLifetime = 60 * 60 * 8, + AllowOfflineAccess = true, + UpdateAccessTokenClaimsOnRefresh = true, + ClientName = "Traffic Police React App", + ClientId = "traffic-police-react-app", + AllowedGrantTypes = GrantTypes.Code, + RedirectUris = + { + "https://localhost:3000/signin-oidc" + }, + AllowedScopes = + { + IdentityServerConstants.StandardScopes.OpenId, + IdentityServerConstants.StandardScopes.Profile, + IdentityServerConstants.StandardScopes.Email, + IdentityServerConstants.StandardScopes.Address, + "traffic-police-api", + }, + RequirePkce = true, + PostLogoutRedirectUris = + { + "https://localhost:3000/signout-callback-oidc" + }, + + RequireConsent = false, + }, + new Client + { + AccessTokenLifetime = 60 * 60 * 8, + AllowOfflineAccess = true, + UpdateAccessTokenClaimsOnRefresh = true, + ClientName = "Tax ASP.NET Core Server Side App", + ClientId = "tax-asp-net-core-app", + AllowedGrantTypes = GrantTypes.Code, + RedirectUris = + { + "https://localhost:7001/signin-oidc" + }, + AllowedScopes = + { + IdentityServerConstants.StandardScopes.OpenId, + IdentityServerConstants.StandardScopes.Profile, + IdentityServerConstants.StandardScopes.Address, + IdentityServerConstants.StandardScopes.Email, + "traffic-police-api", + }, + ClientSecrets = + { + new Secret("secret".Sha256()) + }, + RequirePkce = true, + PostLogoutRedirectUris = + { + "https://localhost:7001/signout-callback-oidc" + }, + + RequireConsent = false, + } + }; + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/OidcSamples.AuthorizationServer.csproj b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/OidcSamples.AuthorizationServer.csproj new file mode 100644 index 0000000..19aade1 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/OidcSamples.AuthorizationServer.csproj @@ -0,0 +1,14 @@ + + + + netcoreapp3.1 + + + + + + + + + + diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Program.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Program.cs new file mode 100644 index 0000000..eeb934c --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Program.cs @@ -0,0 +1,53 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; +using Serilog; +using Serilog.Events; +using Serilog.Sinks.SystemConsole.Themes; +using System; + +namespace OidcSamples.AuthorizationServer +{ + public class Program + { + public static int Main(string[] args) + { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information) + .MinimumLevel.Override("System", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code) + .CreateLogger(); + + try + { + Log.Information("Starting host..."); + CreateHostBuilder(args).Build().Run(); + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly."); + return 1; + } + finally + { + Log.CloseAndFlush(); + } + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseSerilog() + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Properties/launchSettings.json b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Properties/launchSettings.json new file mode 100644 index 0000000..59dcdbe --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "SelfHost": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:10000" + } + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/AccountController.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/AccountController.cs new file mode 100644 index 0000000..2d51c3c --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/AccountController.cs @@ -0,0 +1,368 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using IdentityModel; +using IdentityServer4; +using IdentityServer4.Events; +using IdentityServer4.Extensions; +using IdentityServer4.Models; +using IdentityServer4.Services; +using IdentityServer4.Stores; +using IdentityServer4.Test; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace IdentityServerHost.Quickstart.UI +{ + /// + /// This sample controller implements a typical login/logout/provision workflow for local and external accounts. + /// The login service encapsulates the interactions with the user data store. This data store is in-memory only and cannot be used for production! + /// The interaction service provides a way for the UI to communicate with identityserver for validation and context retrieval + /// + [SecurityHeaders] + [AllowAnonymous] + public class AccountController : Controller + { + private readonly TestUserStore _users; + private readonly IIdentityServerInteractionService _interaction; + private readonly IClientStore _clientStore; + private readonly IAuthenticationSchemeProvider _schemeProvider; + private readonly IEventService _events; + + public AccountController( + IIdentityServerInteractionService interaction, + IClientStore clientStore, + IAuthenticationSchemeProvider schemeProvider, + IEventService events, + TestUserStore users = null) + { + // if the TestUserStore is not in DI, then we'll just use the global users collection + // this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity) + _users = users ?? new TestUserStore(TestUsers.Users); + + _interaction = interaction; + _clientStore = clientStore; + _schemeProvider = schemeProvider; + _events = events; + } + + /// + /// Entry point into the login workflow + /// + [HttpGet] + public async Task Login(string returnUrl) + { + // build a model so we know what to show on the login page + var vm = await BuildLoginViewModelAsync(returnUrl); + + if (vm.IsExternalLoginOnly) + { + // we only have one option for logging in and it's an external provider + return RedirectToAction("Challenge", "External", new { scheme = vm.ExternalLoginScheme, returnUrl }); + } + + return View(vm); + } + + /// + /// Handle postback from username/password login + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Login(LoginInputModel model, string button) + { + // check if we are in the context of an authorization request + var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); + + // the user clicked the "cancel" button + if (button != "login") + { + if (context != null) + { + // if the user cancels, send a result back into IdentityServer as if they + // denied the consent (even if this client does not require consent). + // this will send back an access denied OIDC error response to the client. + await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied); + + // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null + if (context.IsNativeClient()) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage("Redirect", model.ReturnUrl); + } + + return Redirect(model.ReturnUrl); + } + else + { + // since we don't have a valid context, then we just go back to the home page + return Redirect("~/"); + } + } + + if (ModelState.IsValid) + { + // validate username/password against in-memory store + if (_users.ValidateCredentials(model.Username, model.Password)) + { + var user = _users.FindByUsername(model.Username); + await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username, clientId: context?.Client.ClientId)); + + // only set explicit expiration here if user chooses "remember me". + // otherwise we rely upon expiration configured in cookie middleware. + AuthenticationProperties props = null; + if (AccountOptions.AllowRememberLogin && model.RememberLogin) + { + props = new AuthenticationProperties + { + IsPersistent = true, + ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration) + }; + }; + + // issue authentication cookie with subject ID and username + var isuser = new IdentityServerUser(user.SubjectId) + { + DisplayName = user.Username + }; + + await HttpContext.SignInAsync(isuser, props); + + if (context != null) + { + if (context.IsNativeClient()) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage("Redirect", model.ReturnUrl); + } + + // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null + return Redirect(model.ReturnUrl); + } + + // request for a local page + if (Url.IsLocalUrl(model.ReturnUrl)) + { + return Redirect(model.ReturnUrl); + } + else if (string.IsNullOrEmpty(model.ReturnUrl)) + { + return Redirect("~/"); + } + else + { + // user might have clicked on a malicious link - should be logged + throw new Exception("invalid return URL"); + } + } + + await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials", clientId:context?.Client.ClientId)); + ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage); + } + + // something went wrong, show form with error + var vm = await BuildLoginViewModelAsync(model); + return View(vm); + } + + + /// + /// Show logout page + /// + [HttpGet] + public async Task Logout(string logoutId) + { + // build a model so the logout page knows what to display + var vm = await BuildLogoutViewModelAsync(logoutId); + + if (vm.ShowLogoutPrompt == false) + { + // if the request for logout was properly authenticated from IdentityServer, then + // we don't need to show the prompt and can just log the user out directly. + return await Logout(vm); + } + + return View(vm); + } + + /// + /// Handle logout page postback + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Logout(LogoutInputModel model) + { + // build a model so the logged out page knows what to display + var vm = await BuildLoggedOutViewModelAsync(model.LogoutId); + + if (User?.Identity.IsAuthenticated == true) + { + // delete local authentication cookie + await HttpContext.SignOutAsync(); + + // raise the logout event + await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); + } + + // check if we need to trigger sign-out at an upstream identity provider + if (vm.TriggerExternalSignout) + { + // build a return URL so the upstream provider will redirect back + // to us after the user has logged out. this allows us to then + // complete our single sign-out processing. + string url = Url.Action("Logout", new { logoutId = vm.LogoutId }); + + // this triggers a redirect to the external provider for sign-out + return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme); + } + + return View("LoggedOut", vm); + } + + [HttpGet] + public IActionResult AccessDenied() + { + return View(); + } + + + /*****************************************/ + /* helper APIs for the AccountController */ + /*****************************************/ + private async Task BuildLoginViewModelAsync(string returnUrl) + { + var context = await _interaction.GetAuthorizationContextAsync(returnUrl); + if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null) + { + var local = context.IdP == IdentityServer4.IdentityServerConstants.LocalIdentityProvider; + + // this is meant to short circuit the UI and only trigger the one external IdP + var vm = new LoginViewModel + { + EnableLocalLogin = local, + ReturnUrl = returnUrl, + Username = context?.LoginHint, + }; + + if (!local) + { + vm.ExternalProviders = new[] { new ExternalProvider { AuthenticationScheme = context.IdP } }; + } + + return vm; + } + + var schemes = await _schemeProvider.GetAllSchemesAsync(); + + var providers = schemes + .Where(x => x.DisplayName != null) + .Select(x => new ExternalProvider + { + DisplayName = x.DisplayName ?? x.Name, + AuthenticationScheme = x.Name + }).ToList(); + + var allowLocal = true; + if (context?.Client.ClientId != null) + { + var client = await _clientStore.FindEnabledClientByIdAsync(context.Client.ClientId); + if (client != null) + { + allowLocal = client.EnableLocalLogin; + + if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any()) + { + providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList(); + } + } + } + + return new LoginViewModel + { + AllowRememberLogin = AccountOptions.AllowRememberLogin, + EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin, + ReturnUrl = returnUrl, + Username = context?.LoginHint, + ExternalProviders = providers.ToArray() + }; + } + + private async Task BuildLoginViewModelAsync(LoginInputModel model) + { + var vm = await BuildLoginViewModelAsync(model.ReturnUrl); + vm.Username = model.Username; + vm.RememberLogin = model.RememberLogin; + return vm; + } + + private async Task BuildLogoutViewModelAsync(string logoutId) + { + var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt }; + + if (User?.Identity.IsAuthenticated != true) + { + // if the user is not authenticated, then just show logged out page + vm.ShowLogoutPrompt = false; + return vm; + } + + var context = await _interaction.GetLogoutContextAsync(logoutId); + if (context?.ShowSignoutPrompt == false) + { + // it's safe to automatically sign-out + vm.ShowLogoutPrompt = false; + return vm; + } + + // show the logout prompt. this prevents attacks where the user + // is automatically signed out by another malicious web page. + return vm; + } + + private async Task BuildLoggedOutViewModelAsync(string logoutId) + { + // get context information (client name, post logout redirect URI and iframe for federated signout) + var logout = await _interaction.GetLogoutContextAsync(logoutId); + + var vm = new LoggedOutViewModel + { + AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut, + PostLogoutRedirectUri = logout?.PostLogoutRedirectUri, + ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName, + SignOutIframeUrl = logout?.SignOutIFrameUrl, + LogoutId = logoutId + }; + + if (User?.Identity.IsAuthenticated == true) + { + var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; + if (idp != null && idp != IdentityServer4.IdentityServerConstants.LocalIdentityProvider) + { + var providerSupportsSignout = await HttpContext.GetSchemeSupportsSignOutAsync(idp); + if (providerSupportsSignout) + { + if (vm.LogoutId == null) + { + // if there's no current logout context, we need to create one + // this captures necessary info from the current logged in user + // before we signout and redirect away to the external IdP for signout + vm.LogoutId = await _interaction.CreateLogoutContextAsync(); + } + + vm.ExternalAuthenticationScheme = idp; + } + } + } + + return vm; + } + } +} diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/AccountOptions.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/AccountOptions.cs new file mode 100644 index 0000000..a89c229 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/AccountOptions.cs @@ -0,0 +1,20 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using System; + +namespace IdentityServerHost.Quickstart.UI +{ + public class AccountOptions + { + public static bool AllowLocalLogin = true; + public static bool AllowRememberLogin = true; + public static TimeSpan RememberMeLoginDuration = TimeSpan.FromDays(30); + + public static bool ShowLogoutPrompt = true; + public static bool AutomaticRedirectAfterSignOut = true; + + public static string InvalidCredentialsErrorMessage = "Invalid username or password"; + } +} diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/ExternalController.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/ExternalController.cs new file mode 100644 index 0000000..1a7479e --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/ExternalController.cs @@ -0,0 +1,196 @@ +using IdentityModel; +using IdentityServer4; +using IdentityServer4.Events; +using IdentityServer4.Services; +using IdentityServer4.Stores; +using IdentityServer4.Test; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace IdentityServerHost.Quickstart.UI +{ + [SecurityHeaders] + [AllowAnonymous] + public class ExternalController : Controller + { + private readonly TestUserStore _users; + private readonly IIdentityServerInteractionService _interaction; + private readonly IClientStore _clientStore; + private readonly ILogger _logger; + private readonly IEventService _events; + + public ExternalController( + IIdentityServerInteractionService interaction, + IClientStore clientStore, + IEventService events, + ILogger logger, + TestUserStore users = null) + { + // if the TestUserStore is not in DI, then we'll just use the global users collection + // this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity) + _users = users ?? new TestUserStore(TestUsers.Users); + + _interaction = interaction; + _clientStore = clientStore; + _logger = logger; + _events = events; + } + + /// + /// initiate roundtrip to external authentication provider + /// + [HttpGet] + public IActionResult Challenge(string scheme, string returnUrl) + { + if (string.IsNullOrEmpty(returnUrl)) returnUrl = "~/"; + + // validate returnUrl - either it is a valid OIDC URL or back to a local page + if (Url.IsLocalUrl(returnUrl) == false && _interaction.IsValidReturnUrl(returnUrl) == false) + { + // user might have clicked on a malicious link - should be logged + throw new Exception("invalid return URL"); + } + + // start challenge and roundtrip the return URL and scheme + var props = new AuthenticationProperties + { + RedirectUri = Url.Action(nameof(Callback)), + Items = + { + { "returnUrl", returnUrl }, + { "scheme", scheme }, + } + }; + + return Challenge(props, scheme); + + } + + /// + /// Post processing of external authentication + /// + [HttpGet] + public async Task Callback() + { + // read external identity from the temporary cookie + var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); + if (result?.Succeeded != true) + { + throw new Exception("External authentication error"); + } + + if (_logger.IsEnabled(LogLevel.Debug)) + { + var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}"); + _logger.LogDebug("External claims: {@claims}", externalClaims); + } + + // lookup our user and external provider info + var (user, provider, providerUserId, claims) = FindUserFromExternalProvider(result); + if (user == null) + { + // this might be where you might initiate a custom workflow for user registration + // in this sample we don't show how that would be done, as our sample implementation + // simply auto-provisions new external user + user = AutoProvisionUser(provider, providerUserId, claims); + } + + // this allows us to collect any additional claims or properties + // for the specific protocols used and store them in the local auth cookie. + // this is typically used to store data needed for signout from those protocols. + var additionalLocalClaims = new List(); + var localSignInProps = new AuthenticationProperties(); + ProcessLoginCallback(result, additionalLocalClaims, localSignInProps); + + // issue authentication cookie for user + var isuser = new IdentityServerUser(user.SubjectId) + { + DisplayName = user.Username, + IdentityProvider = provider, + AdditionalClaims = additionalLocalClaims + }; + + await HttpContext.SignInAsync(isuser, localSignInProps); + + // delete temporary cookie used during external authentication + await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); + + // retrieve return URL + var returnUrl = result.Properties.Items["returnUrl"] ?? "~/"; + + // check if external login is in the context of an OIDC request + var context = await _interaction.GetAuthorizationContextAsync(returnUrl); + await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.Client.ClientId)); + + if (context != null) + { + if (context.IsNativeClient()) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage("Redirect", returnUrl); + } + } + + return Redirect(returnUrl); + } + + private (TestUser user, string provider, string providerUserId, IEnumerable claims) FindUserFromExternalProvider(AuthenticateResult result) + { + var externalUser = result.Principal; + + // try to determine the unique id of the external user (issued by the provider) + // the most common claim type for that are the sub claim and the NameIdentifier + // depending on the external provider, some other claim type might be used + var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ?? + externalUser.FindFirst(ClaimTypes.NameIdentifier) ?? + throw new Exception("Unknown userid"); + + // remove the user id claim so we don't include it as an extra claim if/when we provision the user + var claims = externalUser.Claims.ToList(); + claims.Remove(userIdClaim); + + var provider = result.Properties.Items["scheme"]; + var providerUserId = userIdClaim.Value; + + // find external user + var user = _users.FindByExternalProvider(provider, providerUserId); + + return (user, provider, providerUserId, claims); + } + + private TestUser AutoProvisionUser(string provider, string providerUserId, IEnumerable claims) + { + var user = _users.AutoProvisionUser(provider, providerUserId, claims.ToList()); + return user; + } + + // if the external login is OIDC-based, there are certain things we need to preserve to make logout work + // this will be different for WS-Fed, SAML2p or other protocols + private void ProcessLoginCallback(AuthenticateResult externalResult, List localClaims, AuthenticationProperties localSignInProps) + { + // if the external system sent a session id claim, copy it over + // so we can use it for single sign-out + var sid = externalResult.Principal.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId); + if (sid != null) + { + localClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value)); + } + + // if the external provider issued an id_token, we'll keep it for signout + var idToken = externalResult.Properties.GetTokenValue("id_token"); + if (idToken != null) + { + localSignInProps.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = idToken } }); + } + } + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/ExternalProvider.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/ExternalProvider.cs new file mode 100644 index 0000000..0584aa6 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/ExternalProvider.cs @@ -0,0 +1,12 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI +{ + public class ExternalProvider + { + public string DisplayName { get; set; } + public string AuthenticationScheme { get; set; } + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoggedOutViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoggedOutViewModel.cs new file mode 100644 index 0000000..6368832 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoggedOutViewModel.cs @@ -0,0 +1,19 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI +{ + public class LoggedOutViewModel + { + public string PostLogoutRedirectUri { get; set; } + public string ClientName { get; set; } + public string SignOutIframeUrl { get; set; } + + public bool AutomaticRedirectAfterSignOut { get; set; } + + public string LogoutId { get; set; } + public bool TriggerExternalSignout => ExternalAuthenticationScheme != null; + public string ExternalAuthenticationScheme { get; set; } + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoginInputModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoginInputModel.cs new file mode 100644 index 0000000..bcb7853 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoginInputModel.cs @@ -0,0 +1,18 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using System.ComponentModel.DataAnnotations; + +namespace IdentityServerHost.Quickstart.UI +{ + public class LoginInputModel + { + [Required] + public string Username { get; set; } + [Required] + public string Password { get; set; } + public bool RememberLogin { get; set; } + public string ReturnUrl { get; set; } + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoginViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoginViewModel.cs new file mode 100644 index 0000000..9bf6c8f --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LoginViewModel.cs @@ -0,0 +1,22 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace IdentityServerHost.Quickstart.UI +{ + public class LoginViewModel : LoginInputModel + { + public bool AllowRememberLogin { get; set; } = true; + public bool EnableLocalLogin { get; set; } = true; + + public IEnumerable ExternalProviders { get; set; } = Enumerable.Empty(); + public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !String.IsNullOrWhiteSpace(x.DisplayName)); + + public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1; + public string ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null; + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LogoutInputModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LogoutInputModel.cs new file mode 100644 index 0000000..300b5f0 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LogoutInputModel.cs @@ -0,0 +1,11 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI +{ + public class LogoutInputModel + { + public string LogoutId { get; set; } + } +} diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LogoutViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LogoutViewModel.cs new file mode 100644 index 0000000..236cd6c --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/LogoutViewModel.cs @@ -0,0 +1,11 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI +{ + public class LogoutViewModel : LogoutInputModel + { + public bool ShowLogoutPrompt { get; set; } = true; + } +} diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/RedirectViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/RedirectViewModel.cs new file mode 100644 index 0000000..904ae20 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Account/RedirectViewModel.cs @@ -0,0 +1,12 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + + +namespace IdentityServerHost.Quickstart.UI +{ + public class RedirectViewModel + { + public string RedirectUrl { get; set; } + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentController.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentController.cs new file mode 100644 index 0000000..6c3aa44 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentController.cs @@ -0,0 +1,262 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using IdentityServer4.Events; +using IdentityServer4.Models; +using IdentityServer4.Services; +using IdentityServer4.Extensions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.Validation; +using System.Collections.Generic; +using System; + +namespace IdentityServerHost.Quickstart.UI +{ + /// + /// This controller processes the consent UI + /// + [SecurityHeaders] + [Authorize] + public class ConsentController : Controller + { + private readonly IIdentityServerInteractionService _interaction; + private readonly IEventService _events; + private readonly ILogger _logger; + + public ConsentController( + IIdentityServerInteractionService interaction, + IEventService events, + ILogger logger) + { + _interaction = interaction; + _events = events; + _logger = logger; + } + + /// + /// Shows the consent screen + /// + /// + /// + [HttpGet] + public async Task Index(string returnUrl) + { + var vm = await BuildViewModelAsync(returnUrl); + if (vm != null) + { + return View("Index", vm); + } + + return View("Error"); + } + + /// + /// Handles the consent screen postback + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Index(ConsentInputModel model) + { + var result = await ProcessConsent(model); + + if (result.IsRedirect) + { + var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); + if (context?.IsNativeClient() == true) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage("Redirect", result.RedirectUri); + } + + return Redirect(result.RedirectUri); + } + + if (result.HasValidationError) + { + ModelState.AddModelError(string.Empty, result.ValidationError); + } + + if (result.ShowView) + { + return View("Index", result.ViewModel); + } + + return View("Error"); + } + + /*****************************************/ + /* helper APIs for the ConsentController */ + /*****************************************/ + private async Task ProcessConsent(ConsentInputModel model) + { + var result = new ProcessConsentResult(); + + // validate return url is still valid + var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); + if (request == null) return result; + + ConsentResponse grantedConsent = null; + + // user clicked 'no' - send back the standard 'access_denied' response + if (model?.Button == "no") + { + grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied }; + + // emit event + await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + } + // user clicked 'yes' - validate the data + else if (model?.Button == "yes") + { + // if the user consented to some scope, build the response model + if (model.ScopesConsented != null && model.ScopesConsented.Any()) + { + var scopes = model.ScopesConsented; + if (ConsentOptions.EnableOfflineAccess == false) + { + scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess); + } + + grantedConsent = new ConsentResponse + { + RememberConsent = model.RememberConsent, + ScopesValuesConsented = scopes.ToArray(), + Description = model.Description + }; + + // emit event + await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); + } + else + { + result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; + } + } + else + { + result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; + } + + if (grantedConsent != null) + { + // communicate outcome of consent back to identityserver + await _interaction.GrantConsentAsync(request, grantedConsent); + + // indicate that's it ok to redirect back to authorization endpoint + result.RedirectUri = model.ReturnUrl; + result.Client = request.Client; + } + else + { + // we need to redisplay the consent UI + result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model); + } + + return result; + } + + private async Task BuildViewModelAsync(string returnUrl, ConsentInputModel model = null) + { + var request = await _interaction.GetAuthorizationContextAsync(returnUrl); + if (request != null) + { + return CreateConsentViewModel(model, returnUrl, request); + } + else + { + _logger.LogError("No consent request matching request: {0}", returnUrl); + } + + return null; + } + + private ConsentViewModel CreateConsentViewModel( + ConsentInputModel model, string returnUrl, + AuthorizationRequest request) + { + var vm = new ConsentViewModel + { + RememberConsent = model?.RememberConsent ?? true, + ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(), + Description = model?.Description, + + ReturnUrl = returnUrl, + + ClientName = request.Client.ClientName ?? request.Client.ClientId, + ClientUrl = request.Client.ClientUri, + ClientLogoUrl = request.Client.LogoUri, + AllowRememberConsent = request.Client.AllowRememberConsent + }; + + vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); + + var apiScopes = new List(); + foreach(var parsedScope in request.ValidatedResources.ParsedScopes) + { + var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); + if (apiScope != null) + { + var scopeVm = CreateScopeViewModel(parsedScope, apiScope, vm.ScopesConsented.Contains(parsedScope.RawValue) || model == null); + apiScopes.Add(scopeVm); + } + } + if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) + { + apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)); + } + vm.ApiScopes = apiScopes; + + return vm; + } + + private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) + { + return new ScopeViewModel + { + Value = identity.Name, + DisplayName = identity.DisplayName ?? identity.Name, + Description = identity.Description, + Emphasize = identity.Emphasize, + Required = identity.Required, + Checked = check || identity.Required + }; + } + + public ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) + { + var displayName = apiScope.DisplayName ?? apiScope.Name; + if (!String.IsNullOrWhiteSpace(parsedScopeValue.ParsedParameter)) + { + displayName += ":" + parsedScopeValue.ParsedParameter; + } + + return new ScopeViewModel + { + Value = parsedScopeValue.RawValue, + DisplayName = displayName, + Description = apiScope.Description, + Emphasize = apiScope.Emphasize, + Required = apiScope.Required, + Checked = check || apiScope.Required + }; + } + + private ScopeViewModel GetOfflineAccessScope(bool check) + { + return new ScopeViewModel + { + Value = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, + DisplayName = ConsentOptions.OfflineAccessDisplayName, + Description = ConsentOptions.OfflineAccessDescription, + Emphasize = true, + Checked = check + }; + } + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentInputModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentInputModel.cs new file mode 100644 index 0000000..ee2d5eb --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentInputModel.cs @@ -0,0 +1,17 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using System.Collections.Generic; + +namespace IdentityServerHost.Quickstart.UI +{ + public class ConsentInputModel + { + public string Button { get; set; } + public IEnumerable ScopesConsented { get; set; } + public bool RememberConsent { get; set; } + public string ReturnUrl { get; set; } + public string Description { get; set; } + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentOptions.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentOptions.cs new file mode 100644 index 0000000..3635d0e --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentOptions.cs @@ -0,0 +1,16 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI +{ + public class ConsentOptions + { + public static bool EnableOfflineAccess = true; + public static string OfflineAccessDisplayName = "Offline Access"; + public static string OfflineAccessDescription = "Access to your applications and resources, even when you are offline"; + + public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission"; + public static readonly string InvalidSelectionErrorMessage = "Invalid selection"; + } +} diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentViewModel.cs new file mode 100644 index 0000000..44c41bd --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ConsentViewModel.cs @@ -0,0 +1,19 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using System.Collections.Generic; + +namespace IdentityServerHost.Quickstart.UI +{ + public class ConsentViewModel : ConsentInputModel + { + public string ClientName { get; set; } + public string ClientUrl { get; set; } + public string ClientLogoUrl { get; set; } + public bool AllowRememberConsent { get; set; } + + public IEnumerable IdentityScopes { get; set; } + public IEnumerable ApiScopes { get; set; } + } +} diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ProcessConsentResult.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ProcessConsentResult.cs new file mode 100644 index 0000000..1d331df --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ProcessConsentResult.cs @@ -0,0 +1,21 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using IdentityServer4.Models; + +namespace IdentityServerHost.Quickstart.UI +{ + public class ProcessConsentResult + { + public bool IsRedirect => RedirectUri != null; + public string RedirectUri { get; set; } + public Client Client { get; set; } + + public bool ShowView => ViewModel != null; + public ConsentViewModel ViewModel { get; set; } + + public bool HasValidationError => ValidationError != null; + public string ValidationError { get; set; } + } +} diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ScopeViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ScopeViewModel.cs new file mode 100644 index 0000000..fa57988 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Consent/ScopeViewModel.cs @@ -0,0 +1,16 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI +{ + public class ScopeViewModel + { + public string Value { get; set; } + public string DisplayName { get; set; } + public string Description { get; set; } + public bool Emphasize { get; set; } + public bool Required { get; set; } + public bool Checked { get; set; } + } +} diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceAuthorizationInputModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceAuthorizationInputModel.cs new file mode 100644 index 0000000..a221181 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceAuthorizationInputModel.cs @@ -0,0 +1,11 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI +{ + public class DeviceAuthorizationInputModel : ConsentInputModel + { + public string UserCode { get; set; } + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceAuthorizationViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceAuthorizationViewModel.cs new file mode 100644 index 0000000..3e8857f --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceAuthorizationViewModel.cs @@ -0,0 +1,12 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI +{ + public class DeviceAuthorizationViewModel : ConsentViewModel + { + public string UserCode { get; set; } + public bool ConfirmUserCode { get; set; } + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceController.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceController.cs new file mode 100644 index 0000000..d7d07ae --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Device/DeviceController.cs @@ -0,0 +1,232 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.Configuration; +using IdentityServer4.Events; +using IdentityServer4.Extensions; +using IdentityServer4.Models; +using IdentityServer4.Services; +using IdentityServer4.Validation; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace IdentityServerHost.Quickstart.UI +{ + [Authorize] + [SecurityHeaders] + public class DeviceController : Controller + { + private readonly IDeviceFlowInteractionService _interaction; + private readonly IEventService _events; + private readonly IOptions _options; + private readonly ILogger _logger; + + public DeviceController( + IDeviceFlowInteractionService interaction, + IEventService eventService, + IOptions options, + ILogger logger) + { + _interaction = interaction; + _events = eventService; + _options = options; + _logger = logger; + } + + [HttpGet] + public async Task Index() + { + string userCodeParamName = _options.Value.UserInteraction.DeviceVerificationUserCodeParameter; + string userCode = Request.Query[userCodeParamName]; + if (string.IsNullOrWhiteSpace(userCode)) return View("UserCodeCapture"); + + var vm = await BuildViewModelAsync(userCode); + if (vm == null) return View("Error"); + + vm.ConfirmUserCode = true; + return View("UserCodeConfirmation", vm); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task UserCodeCapture(string userCode) + { + var vm = await BuildViewModelAsync(userCode); + if (vm == null) return View("Error"); + + return View("UserCodeConfirmation", vm); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Callback(DeviceAuthorizationInputModel model) + { + if (model == null) throw new ArgumentNullException(nameof(model)); + + var result = await ProcessConsent(model); + if (result.HasValidationError) return View("Error"); + + return View("Success"); + } + + private async Task ProcessConsent(DeviceAuthorizationInputModel model) + { + var result = new ProcessConsentResult(); + + var request = await _interaction.GetAuthorizationContextAsync(model.UserCode); + if (request == null) return result; + + ConsentResponse grantedConsent = null; + + // user clicked 'no' - send back the standard 'access_denied' response + if (model.Button == "no") + { + grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied }; + + // emit event + await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + } + // user clicked 'yes' - validate the data + else if (model.Button == "yes") + { + // if the user consented to some scope, build the response model + if (model.ScopesConsented != null && model.ScopesConsented.Any()) + { + var scopes = model.ScopesConsented; + if (ConsentOptions.EnableOfflineAccess == false) + { + scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess); + } + + grantedConsent = new ConsentResponse + { + RememberConsent = model.RememberConsent, + ScopesValuesConsented = scopes.ToArray(), + Description = model.Description + }; + + // emit event + await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); + } + else + { + result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; + } + } + else + { + result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; + } + + if (grantedConsent != null) + { + // communicate outcome of consent back to identityserver + await _interaction.HandleRequestAsync(model.UserCode, grantedConsent); + + // indicate that's it ok to redirect back to authorization endpoint + result.RedirectUri = model.ReturnUrl; + result.Client = request.Client; + } + else + { + // we need to redisplay the consent UI + result.ViewModel = await BuildViewModelAsync(model.UserCode, model); + } + + return result; + } + + private async Task BuildViewModelAsync(string userCode, DeviceAuthorizationInputModel model = null) + { + var request = await _interaction.GetAuthorizationContextAsync(userCode); + if (request != null) + { + return CreateConsentViewModel(userCode, model, request); + } + + return null; + } + + private DeviceAuthorizationViewModel CreateConsentViewModel(string userCode, DeviceAuthorizationInputModel model, DeviceFlowAuthorizationRequest request) + { + var vm = new DeviceAuthorizationViewModel + { + UserCode = userCode, + Description = model?.Description, + + RememberConsent = model?.RememberConsent ?? true, + ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(), + + ClientName = request.Client.ClientName ?? request.Client.ClientId, + ClientUrl = request.Client.ClientUri, + ClientLogoUrl = request.Client.LogoUri, + AllowRememberConsent = request.Client.AllowRememberConsent + }; + + vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); + + var apiScopes = new List(); + foreach (var parsedScope in request.ValidatedResources.ParsedScopes) + { + var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); + if (apiScope != null) + { + var scopeVm = CreateScopeViewModel(parsedScope, apiScope, vm.ScopesConsented.Contains(parsedScope.RawValue) || model == null); + apiScopes.Add(scopeVm); + } + } + if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) + { + apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)); + } + vm.ApiScopes = apiScopes; + + return vm; + } + + private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) + { + return new ScopeViewModel + { + Value = identity.Name, + DisplayName = identity.DisplayName ?? identity.Name, + Description = identity.Description, + Emphasize = identity.Emphasize, + Required = identity.Required, + Checked = check || identity.Required + }; + } + + public ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) + { + return new ScopeViewModel + { + Value = parsedScopeValue.RawValue, + // todo: use the parsed scope value in the display? + DisplayName = apiScope.DisplayName ?? apiScope.Name, + Description = apiScope.Description, + Emphasize = apiScope.Emphasize, + Required = apiScope.Required, + Checked = check || apiScope.Required + }; + } + private ScopeViewModel GetOfflineAccessScope(bool check) + { + return new ScopeViewModel + { + Value = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, + DisplayName = ConsentOptions.OfflineAccessDisplayName, + Description = ConsentOptions.OfflineAccessDescription, + Emphasize = true, + Checked = check + }; + } + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Diagnostics/DiagnosticsController.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Diagnostics/DiagnosticsController.cs new file mode 100644 index 0000000..57c2f55 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Diagnostics/DiagnosticsController.cs @@ -0,0 +1,29 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace IdentityServerHost.Quickstart.UI +{ + [SecurityHeaders] + [Authorize] + public class DiagnosticsController : Controller + { + public async Task Index() + { + var localAddresses = new string[] { "127.0.0.1", "::1", HttpContext.Connection.LocalIpAddress.ToString() }; + if (!localAddresses.Contains(HttpContext.Connection.RemoteIpAddress.ToString())) + { + return NotFound(); + } + + var model = new DiagnosticsViewModel(await HttpContext.AuthenticateAsync()); + return View(model); + } + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Diagnostics/DiagnosticsViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Diagnostics/DiagnosticsViewModel.cs new file mode 100644 index 0000000..f43c768 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Diagnostics/DiagnosticsViewModel.cs @@ -0,0 +1,32 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using IdentityModel; +using Microsoft.AspNetCore.Authentication; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text; + +namespace IdentityServerHost.Quickstart.UI +{ + public class DiagnosticsViewModel + { + public DiagnosticsViewModel(AuthenticateResult result) + { + AuthenticateResult = result; + + if (result.Properties.Items.ContainsKey("client_list")) + { + var encoded = result.Properties.Items["client_list"]; + var bytes = Base64Url.Decode(encoded); + var value = Encoding.UTF8.GetString(bytes); + + Clients = JsonConvert.DeserializeObject(value); + } + } + + public AuthenticateResult AuthenticateResult { get; } + public IEnumerable Clients { get; } = new List(); + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Extensions.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Extensions.cs new file mode 100644 index 0000000..6c720b7 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Extensions.cs @@ -0,0 +1,27 @@ +using System; +using IdentityServer4.Models; +using Microsoft.AspNetCore.Mvc; + +namespace IdentityServerHost.Quickstart.UI +{ + public static class Extensions + { + /// + /// Checks if the redirect URI is for a native client. + /// + /// + public static bool IsNativeClient(this AuthorizationRequest context) + { + return !context.RedirectUri.StartsWith("https", StringComparison.Ordinal) + && !context.RedirectUri.StartsWith("http", StringComparison.Ordinal); + } + + public static IActionResult LoadingPage(this Controller controller, string viewName, string redirectUri) + { + controller.HttpContext.Response.StatusCode = 200; + controller.HttpContext.Response.Headers["Location"] = ""; + + return controller.View(viewName, new RedirectViewModel { RedirectUrl = redirectUri }); + } + } +} diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Grants/GrantsController.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Grants/GrantsController.cs new file mode 100644 index 0000000..128ce59 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Grants/GrantsController.cs @@ -0,0 +1,97 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using IdentityServer4.Services; +using IdentityServer4.Stores; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using IdentityServer4.Events; +using IdentityServer4.Extensions; + +namespace IdentityServerHost.Quickstart.UI +{ + /// + /// This sample controller allows a user to revoke grants given to clients + /// + [SecurityHeaders] + [Authorize] + public class GrantsController : Controller + { + private readonly IIdentityServerInteractionService _interaction; + private readonly IClientStore _clients; + private readonly IResourceStore _resources; + private readonly IEventService _events; + + public GrantsController(IIdentityServerInteractionService interaction, + IClientStore clients, + IResourceStore resources, + IEventService events) + { + _interaction = interaction; + _clients = clients; + _resources = resources; + _events = events; + } + + /// + /// Show list of grants + /// + [HttpGet] + public async Task Index() + { + return View("Index", await BuildViewModelAsync()); + } + + /// + /// Handle postback to revoke a client + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Revoke(string clientId) + { + await _interaction.RevokeUserConsentAsync(clientId); + await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), clientId)); + + return RedirectToAction("Index"); + } + + private async Task BuildViewModelAsync() + { + var grants = await _interaction.GetAllUserGrantsAsync(); + + var list = new List(); + foreach(var grant in grants) + { + var client = await _clients.FindClientByIdAsync(grant.ClientId); + if (client != null) + { + var resources = await _resources.FindResourcesByScopeAsync(grant.Scopes); + + var item = new GrantViewModel() + { + ClientId = client.ClientId, + ClientName = client.ClientName ?? client.ClientId, + ClientLogoUrl = client.LogoUri, + ClientUrl = client.ClientUri, + Description = grant.Description, + Created = grant.CreationTime, + Expires = grant.Expiration, + IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(), + ApiGrantNames = resources.ApiScopes.Select(x => x.DisplayName ?? x.Name).ToArray() + }; + + list.Add(item); + } + } + + return new GrantsViewModel + { + Grants = list + }; + } + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Grants/GrantsViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Grants/GrantsViewModel.cs new file mode 100644 index 0000000..f86354f --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Grants/GrantsViewModel.cs @@ -0,0 +1,27 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using System; +using System.Collections.Generic; + +namespace IdentityServerHost.Quickstart.UI +{ + public class GrantsViewModel + { + public IEnumerable Grants { get; set; } + } + + public class GrantViewModel + { + public string ClientId { get; set; } + public string ClientName { get; set; } + public string ClientUrl { get; set; } + public string ClientLogoUrl { get; set; } + public string Description { get; set; } + public DateTime Created { get; set; } + public DateTime? Expires { get; set; } + public IEnumerable IdentityGrantNames { get; set; } + public IEnumerable ApiGrantNames { get; set; } + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Home/ErrorViewModel.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Home/ErrorViewModel.cs new file mode 100644 index 0000000..7d2cbe3 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Home/ErrorViewModel.cs @@ -0,0 +1,22 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using IdentityServer4.Models; + +namespace IdentityServerHost.Quickstart.UI +{ + public class ErrorViewModel + { + public ErrorViewModel() + { + } + + public ErrorViewModel(string error) + { + Error = new ErrorMessage { Error = error }; + } + + public ErrorMessage Error { get; set; } + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Home/HomeController.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Home/HomeController.cs new file mode 100644 index 0000000..9cf0678 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/Home/HomeController.cs @@ -0,0 +1,65 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using IdentityServer4.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System.Threading.Tasks; + +namespace IdentityServerHost.Quickstart.UI +{ + [SecurityHeaders] + [AllowAnonymous] + public class HomeController : Controller + { + private readonly IIdentityServerInteractionService _interaction; + private readonly IWebHostEnvironment _environment; + private readonly ILogger _logger; + + public HomeController(IIdentityServerInteractionService interaction, IWebHostEnvironment environment, ILogger logger) + { + _interaction = interaction; + _environment = environment; + _logger = logger; + } + + public IActionResult Index() + { + if (_environment.IsDevelopment()) + { + // only show in development + return View(); + } + + _logger.LogInformation("Homepage is disabled in production. Returning 404."); + return NotFound(); + } + + /// + /// Shows the error page + /// + public async Task Error(string errorId) + { + var vm = new ErrorViewModel(); + + // retrieve error details from identityserver + var message = await _interaction.GetErrorContextAsync(errorId); + if (message != null) + { + vm.Error = message; + + if (!_environment.IsDevelopment()) + { + // only show in development + message.ErrorDescription = null; + } + } + + return View("Error", vm); + } + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/SecurityHeadersAttribute.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/SecurityHeadersAttribute.cs new file mode 100644 index 0000000..382c340 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/SecurityHeadersAttribute.cs @@ -0,0 +1,56 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace IdentityServerHost.Quickstart.UI +{ + public class SecurityHeadersAttribute : ActionFilterAttribute + { + public override void OnResultExecuting(ResultExecutingContext context) + { + var result = context.Result; + if (result is ViewResult) + { + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options + if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options")) + { + context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff"); + } + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options + if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options")) + { + context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); + } + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + var csp = "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';"; + // also consider adding upgrade-insecure-requests once you have HTTPS in place for production + //csp += "upgrade-insecure-requests;"; + // also an example if you need client images to be displayed from twitter + // csp += "img-src 'self' https://pbs.twimg.com;"; + + // once for standards compliant browsers + if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy")) + { + context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp); + } + // and once again for IE + if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy")) + { + context.HttpContext.Response.Headers.Add("X-Content-Security-Policy", csp); + } + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy + var referrer_policy = "no-referrer"; + if (!context.HttpContext.Response.Headers.ContainsKey("Referrer-Policy")) + { + context.HttpContext.Response.Headers.Add("Referrer-Policy", referrer_policy); + } + } + } + } +} diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/TestUsers.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/TestUsers.cs new file mode 100644 index 0000000..f16672b --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Quickstart/TestUsers.cs @@ -0,0 +1,38 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using IdentityModel; +using IdentityServer4.Test; +using System.Collections.Generic; +using System.Security.Claims; + +namespace IdentityServerHost.Quickstart.UI +{ + public class TestUsers + { + public static List Users + { + get + { + return new List + { + new TestUser + { + SubjectId = "3199711031234", + Username = "3199711031234", + Password = "123", + Claims = + { + new Claim(JwtClaimTypes.Name, "Muhammad Azeez"), + new Claim(JwtClaimTypes.GivenName, "Muhammad"), + new Claim(JwtClaimTypes.FamilyName, "Azeez"), + new Claim(JwtClaimTypes.Email, "muhammad-azeez@outlook.com"), + new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), + } + } + }; + } + } + } +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Startup.cs b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Startup.cs new file mode 100644 index 0000000..fa12954 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Startup.cs @@ -0,0 +1,60 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using IdentityServerHost.Quickstart.UI; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace OidcSamples.AuthorizationServer +{ + public class Startup + { + public IWebHostEnvironment Environment { get; } + + public Startup(IWebHostEnvironment environment) + { + Environment = environment; + } + + public void ConfigureServices(IServiceCollection services) + { + services.AddControllersWithViews(); + + var builder = services.AddIdentityServer(options => + { + // see https://identityserver4.readthedocs.io/en/latest/topics/resources.html + options.EmitStaticAudienceClaim = true; + }) + .AddInMemoryIdentityResources(Config.IdentityResources) + .AddInMemoryApiResources(Config.ApiResources) + .AddInMemoryApiScopes(Config.ApiScopes) + .AddInMemoryClients(Config.Clients) + .AddTestUsers(TestUsers.Users); + + // not recommended for production - you need to store your key material somewhere secure + builder.AddDeveloperSigningCredential(); + } + + public void Configure(IApplicationBuilder app) + { + if (Environment.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseStaticFiles(); + app.UseRouting(); + + app.UseIdentityServer(); + + app.UseAuthorization(); + app.UseEndpoints(endpoints => + { + endpoints.MapDefaultControllerRoute(); + }); + } + } +} diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/AccessDenied.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/AccessDenied.cshtml new file mode 100644 index 0000000..32e6c53 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/AccessDenied.cshtml @@ -0,0 +1,7 @@ + +
+
+

Access Denied

+

You do not have access to that resource.

+
+
\ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/LoggedOut.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/LoggedOut.cshtml new file mode 100644 index 0000000..3cc190b --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/LoggedOut.cshtml @@ -0,0 +1,34 @@ +@model LoggedOutViewModel + +@{ + // set this so the layout rendering sees an anonymous user + ViewData["signed-out"] = true; +} + +
+

+ Logout + You are now logged out +

+ + @if (Model.PostLogoutRedirectUri != null) + { +
+ Click here to return to the + @Model.ClientName application. +
+ } + + @if (Model.SignOutIframeUrl != null) + { + + } +
+ +@section scripts +{ + @if (Model.AutomaticRedirectAfterSignOut) + { + + } +} diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/Login.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/Login.cshtml new file mode 100644 index 0000000..e4ccb1d --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/Login.cshtml @@ -0,0 +1,87 @@ +@model LoginViewModel + + \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/Logout.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/Logout.cshtml new file mode 100644 index 0000000..e74bde6 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Account/Logout.cshtml @@ -0,0 +1,15 @@ +@model LogoutViewModel + +
+
+

Logout

+

Would you like to logut of IdentityServer?

+
+ +
+ +
+ +
+
+
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Consent/Index.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Consent/Index.cshtml new file mode 100644 index 0000000..f8aa10d --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Consent/Index.cshtml @@ -0,0 +1,104 @@ +@model ConsentViewModel + + diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Device/Success.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Device/Success.cshtml new file mode 100644 index 0000000..050dd91 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Device/Success.cshtml @@ -0,0 +1,7 @@ + +
+
+

Success

+

You have successfully authorized the device

+
+
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Device/UserCodeCapture.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Device/UserCodeCapture.cshtml new file mode 100644 index 0000000..6d41261 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Device/UserCodeCapture.cshtml @@ -0,0 +1,23 @@ +@model string + +
+
+

User Code

+

Please enter the code displayed on your device.

+
+ + + +
+
+
+
+ + +
+ + +
+
+
+
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Device/UserCodeConfirmation.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Device/UserCodeConfirmation.cshtml new file mode 100644 index 0000000..e1d3b19 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Device/UserCodeConfirmation.cshtml @@ -0,0 +1,108 @@ +@model DeviceAuthorizationViewModel + +
+
+ @if (Model.ClientLogoUrl != null) + { + + } +

+ @Model.ClientName + is requesting your permission +

+ @if (Model.ConfirmUserCode) + { +

Please confirm that the authorization request quotes the code: @Model.UserCode.

+ } +

Uncheck the permissions you do not wish to grant.

+
+ +
+
+ +
+
+ +
+ +
+
+ @if (Model.IdentityScopes.Any()) + { +
+
+
+ + Personal Information +
+
    + @foreach (var scope in Model.IdentityScopes) + { + + } +
+
+
+ } + + @if (Model.ApiScopes.Any()) + { +
+
+
+ + Application Access +
+
    + @foreach (var scope in Model.ApiScopes) + { + + } +
+
+
+ } + +
+
+
+ + Description +
+
+ +
+
+
+ + @if (Model.AllowRememberConsent) + { +
+
+ + +
+
+ } +
+
+ +
+
+ + +
+
+ @if (Model.ClientUrl != null) + { + + + @Model.ClientName + + } +
+
+
+
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Diagnostics/Index.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Diagnostics/Index.cshtml new file mode 100644 index 0000000..e939c0d --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Diagnostics/Index.cshtml @@ -0,0 +1,64 @@ +@model DiagnosticsViewModel + +
+
+

Authentication Cookie

+
+ +
+
+
+
+

Claims

+
+
+
+ @foreach (var claim in Model.AuthenticateResult.Principal.Claims) + { +
@claim.Type
+
@claim.Value
+ } +
+
+
+
+ +
+
+
+

Properties

+
+
+
+ @foreach (var prop in Model.AuthenticateResult.Properties.Items) + { +
@prop.Key
+
@prop.Value
+ } + @if (Model.Clients.Any()) + { +
Clients
+
+ @{ + var clients = Model.Clients.ToArray(); + for(var i = 0; i < clients.Length; i++) + { + @clients[i] + if (i < clients.Length - 1) + { + , + } + } + } +
+ } +
+
+
+
+
+
+ + + + diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Grants/Index.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Grants/Index.cshtml new file mode 100644 index 0000000..0cfc7ec --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Grants/Index.cshtml @@ -0,0 +1,87 @@ +@model GrantsViewModel + +
+
+

Client Application Permissions

+

Below is the list of applications you have given permission to and the resources they have access to.

+
+ + @if (Model.Grants.Any() == false) + { +
+
+
+ You have not given access to any applications +
+
+
+ } + else + { + foreach (var grant in Model.Grants) + { +
+
+
+
+ @if (grant.ClientLogoUrl != null) + { + + } + @grant.ClientName +
+ +
+
+ + +
+
+
+
+ +
    + @if (grant.Description != null) + { +
  • + @grant.Description +
  • + } +
  • + @grant.Created.ToString("yyyy-MM-dd") +
  • + @if (grant.Expires.HasValue) + { +
  • + @grant.Expires.Value.ToString("yyyy-MM-dd") +
  • + } + @if (grant.IdentityGrantNames.Any()) + { +
  • + +
      + @foreach (var name in grant.IdentityGrantNames) + { +
    • @name
    • + } +
    +
  • + } + @if (grant.ApiGrantNames.Any()) + { +
  • + +
      + @foreach (var name in grant.ApiGrantNames) + { +
    • @name
    • + } +
    +
  • + } +
+
+ } + } +
\ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Home/Index.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Home/Index.cshtml new file mode 100644 index 0000000..36b2bfc --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Home/Index.cshtml @@ -0,0 +1,32 @@ +@using System.Diagnostics + +@{ + var version = FileVersionInfo.GetVersionInfo(typeof(IdentityServer4.Hosting.IdentityServerMiddleware).Assembly.Location).ProductVersion.Split('+').First(); +} + +
+

+ + Welcome to IdentityServer4 + (version @version) +

+ + +
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/Error.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/Error.cshtml new file mode 100644 index 0000000..4c746e7 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/Error.cshtml @@ -0,0 +1,40 @@ +@model ErrorViewModel + +@{ + var error = Model?.Error?.Error; + var errorDescription = Model?.Error?.ErrorDescription; + var request_id = Model?.Error?.RequestId; +} + +
+
+

Error

+
+ +
+
+
+ Sorry, there was an error + + @if (error != null) + { + + + : @error + + + + if (errorDescription != null) + { +
@errorDescription
+ } + } +
+ + @if (request_id != null) + { +
Request Id: @request_id
+ } +
+
+
diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/Redirect.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/Redirect.cshtml new file mode 100644 index 0000000..ecc31c1 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/Redirect.cshtml @@ -0,0 +1,11 @@ +@model RedirectViewModel + +
+
+

You are now being returned to the application

+

Once complete, you may close this tab.

+
+
+ + + diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_Layout.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000..64ba125 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_Layout.cshtml @@ -0,0 +1,28 @@ + + + + + + + + IdentityServer4 + + + + + + + + + + +
+ @RenderBody() +
+ + + + + @RenderSection("scripts", required: false) + + diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_Nav.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_Nav.cshtml new file mode 100644 index 0000000..7ab1f86 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_Nav.cshtml @@ -0,0 +1,33 @@ +@using IdentityServer4.Extensions + +@{ + string name = null; + if (!true.Equals(ViewData["signed-out"])) + { + name = Context.User?.GetDisplayName(); + } +} + + diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_ScopeListItem.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_ScopeListItem.cshtml new file mode 100644 index 0000000..162029e --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_ScopeListItem.cshtml @@ -0,0 +1,34 @@ +@model ScopeViewModel + +
  • + + @if (Model.Required) + { + (required) + } + @if (Model.Description != null) + { + + } +
  • \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_ValidationSummary.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_ValidationSummary.cshtml new file mode 100644 index 0000000..5fd90ba --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/Shared/_ValidationSummary.cshtml @@ -0,0 +1,7 @@ +@if (ViewContext.ModelState.IsValid == false) +{ +
    + Error +
    +
    +} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/_ViewImports.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/_ViewImports.cshtml new file mode 100644 index 0000000..bb20544 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@using IdentityServerHost.Quickstart.UI +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/_ViewStart.cshtml b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/_ViewStart.cshtml new file mode 100644 index 0000000..820a2f6 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/tempkey.jwk b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/tempkey.jwk new file mode 100644 index 0000000..7f9ca46 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/tempkey.jwk @@ -0,0 +1 @@ +{"alg":"RS256","d":"K_F5s3MAkSWSl_lLgKeqYwp9UYU_FNjwejyRrs16Pa4vgHDpflUADPninzZFe23MYMcTNsWbbZPYB6nwLv9SMRa3-W940A75gEh48LY48ZrLe-OFta8jFFEdXnRsLyA8gRfM0-Hz9Ud0OyMehzqqfCtKTVTIcoG70J2L1DhYvTkofbd766REInuBBRQCb7ouj9l8K5almO-zPgTHkKPYV5tji5VCs0seEe2vVMk3xPR8hPQloejdYq0cVMbdp9p34CajRx2OxbxmbggNU_M1FtR0125eBIKWf9iFjTiLquhLcnH8BrDs29H0kYZWNmY6kTh4OOfTTfCzukovrEDf3Q","dp":"tIBdNNX4bEvNISiGn7e4_BUudu9Dk5_X3TSeUhd6fouhOXWZPTFF7xccEV6rBGyQB1Gpgi0iB-OPLkUYOJF4Rlr6AnO3tGV-wmPpLH9O5sKNx54RgbogRjxjgRstrdICIo7wwRp_eO3ZyUnpC2uC1TnaGJoGdD0o3cydj6aFbF0","dq":"v6Ok_Z75FPd-vzd2ACxrjgymGapFMkM--hutJAn4X0XuoIMpkO-G4KSVy4M5P0lsIUs7-o-fRZ-xcqW8WyfFz_gh9Jwa_j5AiI05T2oCCB-NCQXZEYRG1Grf1b0RBQADoLALkXeihNw_zke4IkY7vrbGrjKISAs63r88EWKE_3E","e":"AQAB","kid":"4B936CF39842424A431BF2C03131729D","kty":"RSA","n":"uG6oMJrUcTj_2FLQ_wTi4On882bio5WnfTBpX52E3RIBDzy9ktIeOyZ4pg7dYuVr3mpE2rdLMCvl5FNY22pvpzjsNuEy5a7wO5PANkjqxCwWFASNE_dKZ9iMuSfuWiTQ4zuPVPPhwYcAKEEUpXkJLZ9HvHCPmqb3fzoolsRB_0iZiyORmo120RU4uOWz6PPwAd_llyhGvMtCwr93sPxYDkX0XTP2wv18X7cc79RrwKvZfIFIMGnazNM6_JWgx3atmAfap3sPVCvwyqfV3BbZBN8t7IVmQWcxuWdkC2UG-QMMveVaBhs05kn5wUTVh7jjHTmAUkNN9DcTmYEy7c9oMQ","p":"36mokmJSKNBei0pksonfZS5aTMs5fKcQJHV_zhOsbbqNG5fj0vCdDsSMVJTyEL7D-Ijsr8pt5csBLiD24R4oo0Rk73vaSRLwypsUOXcxVP3r2xP09pk2eUe2LxlKCAiEwrzN9j0MPjaNiyy0vaSglUumSxUR3xWkJYxLwrkYg-c","q":"0xj56aR3DMXx_AigySznKFgSEIY-Ju8I-Q7eBhbFsZQKxH3BF-TXAOjuIplWlVYSJv940Dyx2V-fJSw6haVV2Ts_1vXGWjoH8kpKrqyIHZwybnCSDUaRTN9rkqkuYD_s3Tb041cAtTHUVCkyZzELI8abREym_pqRyafsy3_oMCc","qi":"YBBUxFykVD9vV5bI_Kwj6Jc6dOI42_1niMjyg0phBeMXRXtAJtTrOXFCkMuDAmIGYpeKqC5fqkz74Fmmu6appY2Dx6UBXT2H_ytlydWfa6cRAX5Nvr8ttzOlNCDKlIGFwe2gRMw9mU4_nfCjfb1O4O-_VcWlK4t8YBeUaMqyiFM"} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/css/site.css b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/css/site.css new file mode 100644 index 0000000..e05e77d --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/css/site.css @@ -0,0 +1,24 @@ +.body-container { + margin-top: 60px; + padding-bottom: 40px; } + +.welcome-page li { + list-style: none; + padding: 4px; } + +.logged-out-page iframe { + display: none; + width: 0; + height: 0; } + +.grants-page .card { + margin-top: 20px; + border-bottom: 1px solid lightgray; } + .grants-page .card .card-title { + font-size: 120%; + font-weight: bold; } + .grants-page .card .card-title img { + width: 100px; + height: 100px; } + .grants-page .card label { + font-weight: bold; } diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/css/site.min.css b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/css/site.min.css new file mode 100644 index 0000000..bfc692e --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/css/site.min.css @@ -0,0 +1 @@ +.body-container{margin-top:60px;padding-bottom:40px;}.welcome-page li{list-style:none;padding:4px;}.logged-out-page iframe{display:none;width:0;height:0;}.grants-page .card{margin-top:20px;border-bottom:1px solid #d3d3d3;}.grants-page .card .card-title{font-size:120%;font-weight:bold;}.grants-page .card .card-title img{width:100px;height:100px;}.grants-page .card label{font-weight:bold;} \ No newline at end of file diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/css/site.scss b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/css/site.scss new file mode 100644 index 0000000..ffab645 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/css/site.scss @@ -0,0 +1,42 @@ +.body-container { + margin-top: 60px; + padding-bottom:40px; +} + +.welcome-page { + li { + list-style: none; + padding: 4px; + } +} + +.logged-out-page { + iframe { + display: none; + width: 0; + height: 0; + } +} + +.grants-page { + .card { + margin-top: 20px; + border-bottom: 1px solid lightgray; + + .card-title { + img { + width: 100px; + height: 100px; + } + + font-size: 120%; + font-weight: bold; + } + + label { + font-weight: bold; + } + } +} + + diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/favicon.ico b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ee470e42facff8210727fb7caca55407becc0135 GIT binary patch literal 1150 zcmZuwTS!z<6g@hck8yM!&Zsl@&OLKS$1=k5M+Y>f(jbcrA}oV`>cgK33H&H}Kv5(Z zN?N|n2$JeUixi;;r6mj%rB)CVRD?uSls_Xy?$%Z>X?ccq_ugx-bV(Vh-K0jAfHK4LS;MUkI;{y8Cq>Dr=dFzQ>w!tkQ3dZ1P|rQYw^H$vw5E8tR&G+4dLc?m z^I|{uc2@3gaHePSd%pq-m_sD#un%$0!_`UMT1%AFOSEWx?yV1%ZqR)>{B7O<7UmFt zOSk(Q=R9)ysN2&tP452x{Wfd5$DO4beuee$*Yf{2$A}TG_dlkFxE3O%?N>F|etU99 zXkF<*Z|}7&M-Cswnx-*$`2EYd&jWd*%jKpLnKrz=b?kE74WhEfXJ>=D^RC10B1ovI zsd?|g12r-sK6xDa0ibQxJpWK^md&dy;!I)vSYJ{f(YB^=Pq8Oq+?k$rD-g&##wB6I?{Pz) m)CkcK#5)uTp%)7gn1?Qf;LB5l6}{+nK{L8Wh<7K2VDBGM+S%6t literal 0 HcmV?d00001 diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/icon.jpg b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/icon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e6525028aca7ed7a339915a3986c38034e31ab1c GIT binary patch literal 19482 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 literal 0 HcmV?d00001 diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/icon.png b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cd386d5170151f6fb166bf1eab2486b1ffb3a421 GIT binary patch literal 20796 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 + + Bootstrap logo + +

    + +

    Bootstrap

    + +

    + Sleek, intuitive, and powerful front-end framework for faster and easier web development. +
    + Explore Bootstrap docs » +
    +
    + Report bug + · + Request feature + · + Themes + · + Blog +

    + + +## Table of contents + +- [Quick start](#quick-start) +- [Status](#status) +- [What's included](#whats-included) +- [Bugs and feature requests](#bugs-and-feature-requests) +- [Documentation](#documentation) +- [Contributing](#contributing) +- [Community](#community) +- [Versioning](#versioning) +- [Creators](#creators) +- [Thanks](#thanks) +- [Copyright and license](#copyright-and-license) + + +## Quick start + +Several quick start options are available: + +- [Download the latest release.](https://github.com/twbs/bootstrap/archive/v4.4.1.zip) +- Clone the repo: `git clone https://github.com/twbs/bootstrap.git` +- Install with [npm](https://www.npmjs.com/): `npm install bootstrap` +- Install with [yarn](https://yarnpkg.com/): `yarn add bootstrap@4.4.1` +- Install with [Composer](https://getcomposer.org/): `composer require twbs/bootstrap:4.4.1` +- Install with [NuGet](https://www.nuget.org/): CSS: `Install-Package bootstrap` Sass: `Install-Package bootstrap.sass` + +Read the [Getting started page](https://getbootstrap.com/docs/4.4/getting-started/introduction/) for information on the framework contents, templates and examples, and more. + + +## Status + +[![Slack](https://bootstrap-slack.herokuapp.com/badge.svg)](https://bootstrap-slack.herokuapp.com/) +[![Build Status](https://github.com/twbs/bootstrap/workflows/Tests/badge.svg)](https://github.com/twbs/bootstrap/actions?workflow=Tests) +[![npm version](https://img.shields.io/npm/v/bootstrap.svg)](https://www.npmjs.com/package/bootstrap) +[![Gem version](https://img.shields.io/gem/v/bootstrap.svg)](https://rubygems.org/gems/bootstrap) +[![Meteor Atmosphere](https://img.shields.io/badge/meteor-twbs%3Abootstrap-blue.svg)](https://atmospherejs.com/twbs/bootstrap) +[![Packagist Prerelease](https://img.shields.io/packagist/vpre/twbs/bootstrap.svg)](https://packagist.org/packages/twbs/bootstrap) +[![NuGet](https://img.shields.io/nuget/vpre/bootstrap.svg)](https://www.nuget.org/packages/bootstrap/absoluteLatest) +[![peerDependencies Status](https://img.shields.io/david/peer/twbs/bootstrap.svg)](https://david-dm.org/twbs/bootstrap?type=peer) +[![devDependency Status](https://img.shields.io/david/dev/twbs/bootstrap.svg)](https://david-dm.org/twbs/bootstrap?type=dev) +[![Coverage Status](https://img.shields.io/coveralls/github/twbs/bootstrap/v4-dev.svg)](https://coveralls.io/github/twbs/bootstrap?branch=v4-dev) +[![CSS gzip size](https://img.badgesize.io/twbs/bootstrap/v4-dev/dist/css/bootstrap.min.css?compression=gzip&label=CSS+gzip+size)](https://github.com/twbs/bootstrap/tree/v4-dev/dist/css/bootstrap.min.css) +[![JS gzip size](https://img.badgesize.io/twbs/bootstrap/v4-dev/dist/js/bootstrap.min.js?compression=gzip&label=JS+gzip+size)](https://github.com/twbs/bootstrap/tree/v4-dev/dist/js/bootstrap.min.js) +[![BrowserStack Status](https://www.browserstack.com/automate/badge.svg?badge_key=SkxZcStBeExEdVJqQ2hWYnlWckpkNmNEY213SFp6WHFETWk2bGFuY3pCbz0tLXhqbHJsVlZhQnRBdEpod3NLSDMzaHc9PQ==--3d0b75245708616eb93113221beece33e680b229)](https://www.browserstack.com/automate/public-build/SkxZcStBeExEdVJqQ2hWYnlWckpkNmNEY213SFp6WHFETWk2bGFuY3pCbz0tLXhqbHJsVlZhQnRBdEpod3NLSDMzaHc9PQ==--3d0b75245708616eb93113221beece33e680b229) +[![Backers on Open Collective](https://opencollective.com/bootstrap/backers/badge.svg)](#backers) +[![Sponsors on Open Collective](https://opencollective.com/bootstrap/sponsors/badge.svg)](#sponsors) + + +## What's included + +Within the download you'll find the following directories and files, logically grouping common assets and providing both compiled and minified variations. You'll see something like this: + +```text +bootstrap/ +└── dist/ + ├── css/ + │ ├── bootstrap-grid.css + │ ├── bootstrap-grid.css.map + │ ├── bootstrap-grid.min.css + │ ├── bootstrap-grid.min.css.map + │ ├── bootstrap-reboot.css + │ ├── bootstrap-reboot.css.map + │ ├── bootstrap-reboot.min.css + │ ├── bootstrap-reboot.min.css.map + │ ├── bootstrap.css + │ ├── bootstrap.css.map + │ ├── bootstrap.min.css + │ └── bootstrap.min.css.map + └── js/ + ├── bootstrap.bundle.js + ├── bootstrap.bundle.js.map + ├── bootstrap.bundle.min.js + ├── bootstrap.bundle.min.js.map + ├── bootstrap.js + ├── bootstrap.js.map + ├── bootstrap.min.js + └── bootstrap.min.js.map +``` + +We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [source maps](https://developers.google.com/web/tools/chrome-devtools/javascript/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/), but not [jQuery](https://jquery.com/). + + +## Bugs and feature requests + +Have a bug or a feature request? Please first read the [issue guidelines](https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md#using-the-issue-tracker) and search for existing and closed issues. If your problem or idea is not addressed yet, [please open a new issue](https://github.com/twbs/bootstrap/issues/new). + + +## Documentation + +Bootstrap's documentation, included in this repo in the root directory, is built with [Jekyll](https://jekyllrb.com/) and publicly hosted on GitHub Pages at . The docs may also be run locally. + +Documentation search is powered by [Algolia's DocSearch](https://community.algolia.com/docsearch/). Working on our search? Be sure to set `debug: true` in `site/docs/4.4/assets/js/src/search.js` file. + +### Running documentation locally + +1. Run through the [tooling setup](https://getbootstrap.com/docs/4.4/getting-started/build-tools/#tooling-setup) to install Jekyll (the site builder) and other Ruby dependencies with `bundle install`. +2. Run `npm install` to install Node.js dependencies. +3. Run `npm start` to compile CSS and JavaScript files, generate our docs, and watch for changes. +4. Open `http://localhost:9001` in your browser, and voilà. + +Learn more about using Jekyll by reading its [documentation](https://jekyllrb.com/docs/). + +### Documentation for previous releases + +You can find all our previous releases docs on . + +[Previous releases](https://github.com/twbs/bootstrap/releases) and their documentation are also available for download. + + +## Contributing + +Please read through our [contributing guidelines](https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md). Included are directions for opening issues, coding standards, and notes on development. + +Moreover, if your pull request contains JavaScript patches or features, you must include [relevant unit tests](https://github.com/twbs/bootstrap/tree/master/js/tests). All HTML and CSS should conform to the [Code Guide](https://github.com/mdo/code-guide), maintained by [Mark Otto](https://github.com/mdo). + +Editor preferences are available in the [editor config](https://github.com/twbs/bootstrap/blob/master/.editorconfig) for easy use in common text editors. Read more and download plugins at . + + +## Community + +Get updates on Bootstrap's development and chat with the project maintainers and community members. + +- Follow [@getbootstrap on Twitter](https://twitter.com/getbootstrap). +- Read and subscribe to [The Official Bootstrap Blog](https://blog.getbootstrap.com/). +- Join [the official Slack room](https://bootstrap-slack.herokuapp.com/). +- Chat with fellow Bootstrappers in IRC. On the `irc.freenode.net` server, in the `##bootstrap` channel. +- Implementation help may be found at Stack Overflow (tagged [`bootstrap-4`](https://stackoverflow.com/questions/tagged/bootstrap-4)). +- Developers should use the keyword `bootstrap` on packages which modify or add to the functionality of Bootstrap when distributing through [npm](https://www.npmjs.com/browse/keyword/bootstrap) or similar delivery mechanisms for maximum discoverability. + + +## Versioning + +For transparency into our release cycle and in striving to maintain backward compatibility, Bootstrap is maintained under [the Semantic Versioning guidelines](https://semver.org/). Sometimes we screw up, but we adhere to those rules whenever possible. + +See [the Releases section of our GitHub project](https://github.com/twbs/bootstrap/releases) for changelogs for each release version of Bootstrap. Release announcement posts on [the official Bootstrap blog](https://blog.getbootstrap.com/) contain summaries of the most noteworthy changes made in each release. + + +## Creators + +**Mark Otto** + +- +- + +**Jacob Thornton** + +- +- + + +## Thanks + + + BrowserStack Logo + + +Thanks to [BrowserStack](https://www.browserstack.com/) for providing the infrastructure that allows us to test in real browsers! + + +## Backers + +Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/bootstrap#backer)] + +[![Bakers](https://opencollective.com/bootstrap/backers.svg?width=890)](https://opencollective.com/bootstrap#backers) + + +## Sponsors + +Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/bootstrap#sponsor)] + +[![](https://opencollective.com/bootstrap/sponsor/0/avatar.svg)](https://opencollective.com/bootstrap/sponsor/0/website) +[![](https://opencollective.com/bootstrap/sponsor/1/avatar.svg)](https://opencollective.com/bootstrap/sponsor/1/website) +[![](https://opencollective.com/bootstrap/sponsor/2/avatar.svg)](https://opencollective.com/bootstrap/sponsor/2/website) +[![](https://opencollective.com/bootstrap/sponsor/3/avatar.svg)](https://opencollective.com/bootstrap/sponsor/3/website) +[![](https://opencollective.com/bootstrap/sponsor/4/avatar.svg)](https://opencollective.com/bootstrap/sponsor/4/website) +[![](https://opencollective.com/bootstrap/sponsor/5/avatar.svg)](https://opencollective.com/bootstrap/sponsor/5/website) +[![](https://opencollective.com/bootstrap/sponsor/6/avatar.svg)](https://opencollective.com/bootstrap/sponsor/6/website) +[![](https://opencollective.com/bootstrap/sponsor/7/avatar.svg)](https://opencollective.com/bootstrap/sponsor/7/website) +[![](https://opencollective.com/bootstrap/sponsor/8/avatar.svg)](https://opencollective.com/bootstrap/sponsor/8/website) +[![](https://opencollective.com/bootstrap/sponsor/9/avatar.svg)](https://opencollective.com/bootstrap/sponsor/9/website) + + +## Copyright and license + +Code and documentation copyright 2011-2019 the [Bootstrap Authors](https://github.com/twbs/bootstrap/graphs/contributors) and [Twitter, Inc.](https://twitter.com) Code released under the [MIT License](https://github.com/twbs/bootstrap/blob/master/LICENSE). Docs released under [Creative Commons](https://creativecommons.org/licenses/by/3.0/). diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_alert.scss b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_alert.scss new file mode 100644 index 0000000..da2a98a --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_alert.scss @@ -0,0 +1,51 @@ +// +// Base styles +// + +.alert { + position: relative; + padding: $alert-padding-y $alert-padding-x; + margin-bottom: $alert-margin-bottom; + border: $alert-border-width solid transparent; + @include border-radius($alert-border-radius); +} + +// Headings for larger alerts +.alert-heading { + // Specified to prevent conflicts of changing $headings-color + color: inherit; +} + +// Provide class for links that match alerts +.alert-link { + font-weight: $alert-link-font-weight; +} + + +// Dismissible alerts +// +// Expand the right padding and account for the close button's positioning. + +.alert-dismissible { + padding-right: $close-font-size + $alert-padding-x * 2; + + // Adjust close link position + .close { + position: absolute; + top: 0; + right: 0; + padding: $alert-padding-y $alert-padding-x; + color: inherit; + } +} + + +// Alternate styles +// +// Generate contextual modifier classes for colorizing the alert. + +@each $color, $value in $theme-colors { + .alert-#{$color} { + @include alert-variant(theme-color-level($color, $alert-bg-level), theme-color-level($color, $alert-border-level), theme-color-level($color, $alert-color-level)); + } +} diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_badge.scss b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_badge.scss new file mode 100644 index 0000000..42c5d08 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_badge.scss @@ -0,0 +1,54 @@ +// Base class +// +// Requires one of the contextual, color modifier classes for `color` and +// `background-color`. + +.badge { + display: inline-block; + padding: $badge-padding-y $badge-padding-x; + @include font-size($badge-font-size); + font-weight: $badge-font-weight; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + @include border-radius($badge-border-radius); + @include transition($badge-transition); + + @at-root a#{&} { + @include hover-focus() { + text-decoration: none; + } + } + + // Empty badges collapse automatically + &:empty { + display: none; + } +} + +// Quick fix for badges in buttons +.btn .badge { + position: relative; + top: -1px; +} + +// Pill badges +// +// Make them extra rounded with a modifier to replace v3's badges. + +.badge-pill { + padding-right: $badge-pill-padding-x; + padding-left: $badge-pill-padding-x; + @include border-radius($badge-pill-border-radius); +} + +// Colors +// +// Contextual variations (linked badges get darker on :hover). + +@each $color, $value in $theme-colors { + .badge-#{$color} { + @include badge-variant($value); + } +} diff --git a/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_breadcrumb.scss b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_breadcrumb.scss new file mode 100644 index 0000000..d748894 --- /dev/null +++ b/CSharp/OidcSamples/OidcSamples.AuthorizationServer/wwwroot/lib/bootstrap/scss/_breadcrumb.scss @@ -0,0 +1,42 @@ +.breadcrumb { + display: flex; + flex-wrap: wrap; + padding: $breadcrumb-padding-y $breadcrumb-padding-x; + margin-bottom: $breadcrumb-margin-bottom; + @include font-size($breadcrumb-font-size); + list-style: none; + background-color: $breadcrumb-bg; + @include border-radius($breadcrumb-border-radius); +} + +.breadcrumb-item { + // The separator between breadcrumbs (by default, a forward-slash: "/") + + .breadcrumb-item { + padding-left: $breadcrumb-item-padding; + + &::before { + display: inline-block; // Suppress underlining of the separator in modern browsers + padding-right: $breadcrumb-item-padding; + color: $breadcrumb-divider-color; + content: escape-svg($breadcrumb-divider); + } + } + + // IE9-11 hack to properly handle hyperlink underlines for breadcrumbs built + // without `