Skip to content

Commit

Permalink
v1.1
Browse files Browse the repository at this point in the history
- Minor bugs correction
- Added localization feature to SSO
- Added translation feature to User Management
- Silent refresh for angular-oauth2-oidc both for User Management and Admin
- Changed Security Headers to accept Azure Application Insights
  • Loading branch information
brunobritodev committed Dec 27, 2018
1 parent 13b68be commit 8bca0b3
Show file tree
Hide file tree
Showing 630 changed files with 42,133 additions and 478 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,17 @@ We'll love it! Please [Read the docs](https://jp-project.readthedocs.io/en/lates
If you need help building or running your Jp Project platform
There are several ways we can help you out.

## v1.1
- Minor bugs correction
- Added localization feature to SSO
- Added translation feature to User Management
- Silent refresh for angular-oauth2-oidc both for User Management and Admin
- Changed Security Headers to accept Azure Application Insights


## v1.0
* First release

# What comes next?

Code coverage
Expand Down
22 changes: 22 additions & 0 deletions src/Backend/Jp.UserManagement/jpProject_sso_log.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60761,3 +60761,25 @@ WHERE `s.Client`.`ClientId` = @__clientId_0
2018-10-26 13:57:09.236 -03:00 [INF] Executing ObjectResult, writing value of type 'Jp.Infra.CrossCutting.Tools.Model.DefaultResponse`1[[System.Collections.Generic.IEnumerable`1[[Jp.Application.ViewModels.SecretViewModel, Jp.Application, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'.
2018-10-26 13:57:09.242 -03:00 [INF] Executed action Jp.Management.Controllers.ClientsController.Secrets (Jp.Management) in 55.4131ms
2018-10-26 13:57:09.248 -03:00 [INF] Request finished in 92.4888ms 200 application/json; charset=utf-8
2018-12-27 14:13:18.682 -02:00 [INF] Authority URI: https://localhost:5000
2018-12-27 14:13:19.981 -02:00 [INF] User profile is available. Using 'C:\Users\bruno.brito\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
2018-12-27 15:24:04.070 -02:00 [INF] Authority URI: https://localhost:5000
2018-12-27 15:24:06.355 -02:00 [INF] User profile is available. Using 'C:\Users\bruno.brito\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
2018-12-27 16:09:22.837 -02:00 [INF] Authority URI: https://localhost:5000
2018-12-27 16:09:24.280 -02:00 [INF] User profile is available. Using 'C:\Users\bruno.brito\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
2018-12-27 16:43:56.856 -02:00 [INF] Authority URI: https://localhost:5000
2018-12-27 16:43:58.117 -02:00 [INF] User profile is available. Using 'C:\Users\bruno.brito\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
2018-12-27 16:46:28.770 -02:00 [INF] Authority URI: https://localhost:5000
2018-12-27 16:46:29.882 -02:00 [INF] User profile is available. Using 'C:\Users\bruno.brito\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
2018-12-27 16:56:06.556 -02:00 [INF] Authority URI: https://localhost:5000
2018-12-27 16:56:07.667 -02:00 [INF] User profile is available. Using 'C:\Users\bruno.brito\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
2018-12-27 17:00:56.720 -02:00 [INF] Authority URI: https://localhost:5000
2018-12-27 17:00:57.838 -02:00 [INF] User profile is available. Using 'C:\Users\bruno.brito\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
2018-12-27 17:06:27.089 -02:00 [INF] Authority URI: https://localhost:5000
2018-12-27 17:06:28.265 -02:00 [INF] User profile is available. Using 'C:\Users\bruno.brito\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
2018-12-27 17:10:29.892 -02:00 [INF] Authority URI: https://localhost:5000
2018-12-27 17:10:31.022 -02:00 [INF] User profile is available. Using 'C:\Users\bruno.brito\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
2018-12-27 17:14:12.394 -02:00 [INF] Authority URI: https://localhost:5000
2018-12-27 17:14:13.548 -02:00 [INF] User profile is available. Using 'C:\Users\bruno.brito\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
2018-12-27 18:46:16.995 -02:00 [INF] Authority URI: https://localhost:5000
2018-12-27 18:46:18.413 -02:00 [INF] User profile is available. Using 'C:\Users\bruno.brito\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
5 changes: 4 additions & 1 deletion src/Frontend/Jp.AdminUI/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,14 @@ export class AppComponent implements OnInit {
this.oauthService.configure(authConfig);
this.oauthService.setStorage(localStorage);
this.oauthService.tokenValidationHandler = new JwksValidationHandler();


this.settings.loadDiscoveryDocumentAndTryLogin().pipe(tap(doc => {
if (!environment.production)
console.log(doc);
})).subscribe();
})).subscribe(a => {
this.oauthService.setupAutomaticSilentRefresh();
});
// this.oauthService.loadDiscoveryDocument().then(doc => {
// if (!environment.production)
// console.log(doc);
Expand Down
1 change: 1 addition & 0 deletions src/Frontend/Jp.AdminUI/src/app/core/auth/auth.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const authConfig: AuthConfig = {
clientId: "IS4-Admin",
postLogoutRedirectUri: environment.Uri,
redirectUri: environment.Uri + "/login-callback",
silentRefreshRedirectUri: environment.Uri + '/silent-refresh.html',
scope: "openid profile email jp_api.is4",
oidc: true,
};
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { Injectable } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { Router } from '@angular/router';
import { environment } from '@env/environment';
import { BehaviorSubject, of, from } from 'rxjs';
import { switchMap, catchError, take } from 'rxjs/operators';
import { SettingsService } from '@core/settings/settings.service';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
private baseUrl: string;
isRefreshingToken: boolean = false;
tokenSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(null);

constructor(
private authStorage: OAuthService,
private router: Router) {
public settingsService: SettingsService) {
this.baseUrl = environment.ResourceServer;
}

Expand All @@ -24,16 +29,86 @@ export class AuthInterceptor implements HttpInterceptor {

if (url.startsWith(this.baseUrl.toLowerCase())) {

const token = this.authStorage.getAccessToken();
const header = 'Bearer ' + token;
return next.handle(this.addToken(req, this.authStorage.getAccessToken())).do((event: HttpEvent<any>) => { }, (error: any) => {
if (error instanceof HttpErrorResponse) {
switch ((<HttpErrorResponse>error).status) {
case 400:
return this.handle400Error(error);
case 401:
{
this.settingsService.login();
return Observable.throw(error);
}
}
} else {
return Observable.throw(error);
}
});
}

const headers = req.headers
.set('Authorization', header);
return next.handle(req);
}

req = req.clone({ headers });
private handle400Error(error) {
if (error && error.status === 400 && error.error && error.error.error === 'invalid_grant') {
// If we get a 400 and the error message is 'invalid_grant', the token is no longer valid so logout.
this.authStorage.logOut();
this.settingsService.login();
}

return next.handle(req);
return Observable.throw(error);
}

// In case of silentRefresh()
private handle401Error(req: HttpRequest<any>, next: HttpHandler) {
if (!this.isRefreshingToken) {
this.isRefreshingToken = true;

// Reset here so that the following requests wait until the token
// comes back from the refreshToken call.
this.tokenSubject.next(null);

from(this.authStorage.silentRefresh())
.pipe(switchMap(a => {
this.tokenSubject.next(true);
return next.handle(this.addToken(req, this.authStorage.getAccessToken()));
}))
.do((event: HttpEvent<any>) => { }, (err: any) => {
if (err instanceof HttpErrorResponse) {
// do error handling here
this.settingsService.login();
}
});
} else {
return this.tokenSubject
.filter(token => token != null)
.pipe(take(1))
.pipe(switchMap(token => {
return next.handle(this.addToken(req, this.authStorage.getAccessToken()));
}));
}
}

/*
This method is only here so the example works.
Do not include in your code, just use 'req' instead of 'this.getNewRequest(req)'.
*/
getNewRequest(req: HttpRequest<any>): HttpRequest<any> {
if (req.url.indexOf('getData') > 0) {
return new HttpRequest('GET', 'http://private-4002d-testerrorresponses.apiary-mock.com/getData');
}

return new HttpRequest('GET', 'http://private-4002d-testerrorresponses.apiary-mock.com/getLookup');
}

logoutUser() {
// Route to the login page (implementation up to you)

return Observable.throw("");
}

addToken(req: HttpRequest<any>, token: string): HttpRequest<any> {
return req.clone({ setHeaders: { Authorization: 'Bearer ' + token } });
}

private shouldIgnore(url: string): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Scope } from "@shared/viewModel/scope.model";

@Injectable()
export class ApiResourceService {

constructor(private http: HttpClient) {
}

Expand All @@ -32,14 +32,14 @@ export class ApiResourceService {
}

public update(model: ApiResource): Observable<DefaultResponse<boolean>> {
return this.http.post<DefaultResponse<boolean>>(environment.ResourceServer + "ApiResource/update", model);
return this.http.put<DefaultResponse<boolean>>(environment.ResourceServer + "ApiResource/update", model);
}

public remove(name: string): any {
const removeCommand = {
name: name
};
return this.http.post<DefaultResponse<boolean>>(environment.ResourceServer + "ApiResource/remove", removeCommand);
return this.http.post<DefaultResponse<boolean>>(environment.ResourceServer + "ApiResource/remove", { params: removeCommand });
}


Expand All @@ -51,7 +51,9 @@ export class ApiResourceService {
};
return this.http.get<DefaultResponse<ApiResourceSecret[]>>(environment.ResourceServer + "ApiResource/secrets", options);
}

public removeSecret(resourceName: string, id: number): Observable<DefaultResponse<boolean>> {

const removeCommand = {
id: id,
resourceName: resourceName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ <h3>{{ resourceName }}</h3>
<tr *ngFor="let secret of apiSecrets">
<td><button class="btn btn-danger btn-xs" (click)="remove(secret.id)"><i class="fa fa-times"></i></button></td>
<td>{{secret.value}}</td>
<th>{{ secret.type }}</th>
<td>{{secret.type}}</td>
<td>{{secret.expiration}}</td>
<td>{{secret.description}}</td>
</tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class ApiResourceSecretsComponent implements OnInit {
this.model = new ApiResourceSecret();
this.showButtonLoading = false;
this.hashTypes = [{ id: 0, text: "Sha256" }, { id: 1, text: "Sha512" }];
this.secretTypes = [{ id: 'SharedSecret', text: "Shared Secret" }, { id: "X509Thumbprint", text: "X509 Thumbprint" }, { id: "X509Name", text: "X509 Name" }, { id: "X509CertificateBase64", text: "X509 Certificate Base64" }];
this.secretTypes = [{ id: 'SharedSecret', text: "Shared Secret" }, { id: 'X509Thumbprint', text: "X509 Thumbprint" }, { id: 'X509Name', text: "X509 Name" }, { id: 'X509CertificateBase64', text: "X509 Certificate Base64" }];
}

public showSuccessMessage() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class ClientService {
}

public update(model: Client): Observable<DefaultResponse<boolean>> {
return this.http.post<DefaultResponse<boolean>>(environment.ResourceServer + "clients/update", model);
return this.http.put<DefaultResponse<boolean>>(environment.ResourceServer + "clients/update", model);
}

public remove(clientId: string): any {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ <h3>{{ client }}</h3>
<tr *ngFor="let secret of clientSecrets">
<td><button class="btn btn-danger btn-xs" (click)="remove(secret.id)"><i class="fa fa-times"></i></button></td>
<td>{{secret.value}}</td>
<th>{{ secret.type }}</th>
<td>{{secret.type}}</td>
<td>{{secret.expiration}}</td>
<td>{{secret.description}}</td>
</tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class ClientSecretsComponent implements OnInit {
this.model = new ClientSecret();
this.showButtonLoading = false;
this.hashTypes = [{ id: 0, text: "Sha256" }, { id: 1, text: "Sha512" }];
this.secretTypes = [{ id: 'SharedSecret', text: "Shared Secret" }, { id: "X509Thumbprint", text: "X509 Thumbprint" }, { id: "X509Name", text: "X509 Name" }, { id: "X509CertificateBase64", text: "X509 Certificate Base64" }];
this.secretTypes = [{ id: 'SharedSecret', text: "Shared Secret" }, { id: 'X509Thumbprint', text: "X509 Thumbprint" }, { id: 'X509Name', text: "X509 Name" }, { id: 'X509CertificateBase64', text: "X509 Certificate Base64" }];
}

public showSuccessMessage() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class IdentityResourceService {
}

public update(model: IdentityResource): Observable<DefaultResponse<boolean>> {
return this.http.post<DefaultResponse<boolean>>(environment.ResourceServer + "IdentityResource/update", model);
return this.http.put<DefaultResponse<boolean>>(environment.ResourceServer + "IdentityResource/update", model);
}

public remove(name: string): any {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export class UserLoginsComponent implements OnInit {
public ngOnInit() {
this.route.params.pipe(tap(p => this.userName = p["username"])).pipe(map(p => p["username"])).pipe(flatMap(m => this.userService.getUserLogins(m.toString()))).subscribe(result => this.logins = result.data);
this.errors = [];
this.logins = [];
this.showButtonLoading = false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class RoleService {
}

public update(model: Role): Observable<DefaultResponse<boolean>> {
return this.http.post<DefaultResponse<boolean>>(environment.ResourceServer + "Roles/update", model);
return this.http.put<DefaultResponse<boolean>>(environment.ResourceServer + "Roles/update", model);
}

public removeUserFromRole(user: string, role: string): Observable<DefaultResponse<boolean>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class UserService {

public update(updateCommand: UserProfile): Observable<DefaultResponse<boolean>> {

return this.http.post<DefaultResponse<boolean>>(environment.ResourceServer + "UserAdmin/update", updateCommand);
return this.http.put<DefaultResponse<boolean>>(environment.ResourceServer + "UserAdmin/update", updateCommand);
}
public save(model: UserProfile): Observable<DefaultResponse<boolean>> {

Expand Down Expand Up @@ -133,7 +133,7 @@ export class UserService {
}

public resetPassword(resetPassword: ResetPassword): Observable<DefaultResponse<boolean>> {
return this.http.post<DefaultResponse<boolean>>(environment.ResourceServer + "UserAdmin/reset-password", resetPassword);
return this.http.put<DefaultResponse<boolean>>(environment.ResourceServer + "UserAdmin/reset-password", resetPassword);
}

public showLogs(username: string): Observable<EventHistoryData[]> {
Expand Down
12 changes: 12 additions & 0 deletions src/Frontend/Jp.AdminUI/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,16 @@
<body>
<app-root></app-root>
</body>
<script type="text/javascript">

var appInsights=window.appInsights||function(a){
function b(a){c[a]=function(){var b=arguments;c.queue.push(function(){c[a].apply(c,b)})}}var c={config:a},d=document,e=window;setTimeout(function(){var b=d.createElement("script");b.src=a.url||"https://az416426.vo.msecnd.net/scripts/a/ai.0.js",d.getElementsByTagName("script")[0].parentNode.appendChild(b)});try{c.cookie=d.cookie}catch(a){}c.queue=[];for(var f=["Event","Exception","Metric","PageView","Trace","Dependency"];f.length;)b("track"+f.pop());if(b("setAuthenticatedUserContext"),b("clearAuthenticatedUserContext"),b("startTrackEvent"),b("stopTrackEvent"),b("startTrackPage"),b("stopTrackPage"),b("flush"),!a.disableExceptionTracking){f="onerror",b("_"+f);var g=e[f];e[f]=function(a,b,d,e,h){var i=g&&g(a,b,d,e,h);return!0!==i&&c["_"+f](a,b,d,e,h),i}}return c
}({
instrumentationKey: '205fabcd-09ee-46f5-bb74-95780fc873da'
});

window.appInsights=appInsights,appInsights.queue&&0===appInsights.queue.length&&appInsights.trackPageView();

</script>

</html>
7 changes: 7 additions & 0 deletions src/Frontend/Jp.AdminUI/src/silent-refresh.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<html>
<body>
<script>
parent.postMessage(location.hash, location.origin);
</script>
</body>
</html>
47 changes: 47 additions & 0 deletions src/Frontend/Jp.UI.SSO/Configuration/LocalizationConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Globalization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace Jp.UI.SSO.Configuration
{
public static class LocalizationConfig
{
public static void AddMvcLocalization(this IServiceCollection services)
{

services.AddLocalization(opts => { opts.ResourcesPath = "Resources"; });

services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddViewLocalization(
LanguageViewLocationExpanderFormat.Suffix,
opts => { opts.ResourcesPath = "Resources"; })
.AddDataAnnotationsLocalization();

services.Configure<RequestLocalizationOptions>(
opts =>
{
var supportedCultures = new[]
{
new CultureInfo("pt-BR"),
new CultureInfo("en"),
};

opts.DefaultRequestCulture = new RequestCulture("pt-BR");
opts.SupportedCultures = supportedCultures;
opts.SupportedUICultures = supportedCultures;
});
}

public static void UseLocalization(this IApplicationBuilder app)
{
var options = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
app.UseRequestLocalization(options.Value);
}

}
}
Loading

0 comments on commit 8bca0b3

Please sign in to comment.