diff --git a/Frontend Angular 4/src/app/shared/config.ts b/Frontend Angular 4/src/app/shared/config.ts index 1f030693618757283b799815428add96d54fcfcd..e5b48168ac6ac14bb205b1a53d1043eec5ae2f16 100644 --- a/Frontend Angular 4/src/app/shared/config.ts +++ b/Frontend Angular 4/src/app/shared/config.ts @@ -1,9 +1,9 @@ //export const SERVER = 'https://matefun.mybluemix.net'; //export const GHCI_URL = 'wss://matefun.mybluemix.net/endpoint'; -//export const SERVER = 'http://localhost:9080'; -//export const GHCI_URL = 'ws://localhost:9080/endpoint'; +export const SERVER = 'http://localhost:9090'; +export const GHCI_URL = 'ws://localhost:9090/endpoint'; //Configuracion dinamica pensando en servidor con ip dinamica -export const SERVER = window.location.protocol + '//' + window.location.host;//'http://localhost:9090'; -export const GHCI_URL = window.location.protocol == 'http:'? 'ws://'+window.location.host+'/endpoint': 'wss://'+window.location.host+'/endpoint'; +//export const SERVER = window.location.protocol + '//' + window.location.host;//'http://localhost:9090'; +//export const GHCI_URL = window.location.protocol == 'http:'? 'ws://'+window.location.host+'/endpoint': 'wss://'+window.location.host+'/endpoint'; diff --git a/Frontend Angular 4/src/app/shared/services/authentication.service.ts b/Frontend Angular 4/src/app/shared/services/authentication.service.ts index 8967c6f5b671774b1475fb2ee26bbe4d06895c9b..972b2469cab65df9990f0d4c66fa9cdd5aebe7c5 100644 --- a/Frontend Angular 4/src/app/shared/services/authentication.service.ts +++ b/Frontend Angular 4/src/app/shared/services/authentication.service.ts @@ -15,12 +15,8 @@ export class AuthenticationService { let options = new RequestOptions({ headers: headers }); return this.http.post(SERVER+'/servicios/login', JSON.stringify({ "cedula": cedula, "password": password }),options) .map((response: Response) => { - // login successful if there's a jwt token in the response let user = response.json(); - // if (user && user.token) { - // store user details and jwt token in local storage to keep user logged in between page refreshes - sessionStorage.setItem('currentUser', JSON.stringify(user)); - // } + sessionStorage.setItem('currentUser', JSON.stringify(user)); }); } diff --git a/Frontend Angular 4/src/app/shared/services/ghci.service.ts b/Frontend Angular 4/src/app/shared/services/ghci.service.ts index d8e0e34eae298b2c297388f7de913d3cb8ac8b44..8a24162fc5797be6d7cf3d0d5345faedec6f009d 100644 --- a/Frontend Angular 4/src/app/shared/services/ghci.service.ts +++ b/Frontend Angular 4/src/app/shared/services/ghci.service.ts @@ -1,9 +1,11 @@ import { Injectable,ViewChild,ElementRef } from '@angular/core'; +import { Router } from '@angular/router'; import { Observable, Subject } from 'rxjs/Rx'; import { WebsocketService } from './websocket.service'; import { AuthenticationService } from './authentication.service'; import { GHCI_URL } from '../config'; + declare var $:any; declare var that :any ; const regex = /^color (errores|input|output|logs) (\d)$/g; @@ -32,7 +34,7 @@ export class GHCIService { consoleBuffer = []; - constructor(private authService:AuthenticationService){ + constructor(private authService:AuthenticationService,private router: Router){ console.log("contructor ghci"); this.conectarWS(GHCI_URL, authService.getUser().cedula, authService.getToken()); setInterval( this.checkConnection.bind(this), 5000); @@ -84,9 +86,13 @@ export class GHCIService { this.connection.onopen = function(){ console.log('Conexión con web socket exitosa'); } - this.connection.onclose = function(){ - console.log('Conexión con web socket cerrada'); - } + this.connection.onclose = function(reason){ + //Codigo que indica la falta de permisos (sesion expirada por ejemplo) + if(reason.code == 1008){ + this.router.navigate(['/login']); + } + console.log('Conexión con web socket cerrada',reason); + }.bind(this) this.connection.onmessage = this.onMessage.bind(this); } } diff --git a/Frontend Angular 4/src/app/shared/services/haskell.service.ts b/Frontend Angular 4/src/app/shared/services/haskell.service.ts index 97265288dcc157ec0c5e3ce33f44e7b4fe61f186..d30b27dbd0ce9f2a21f06d4799df684765ff8259 100644 --- a/Frontend Angular 4/src/app/shared/services/haskell.service.ts +++ b/Frontend Angular 4/src/app/shared/services/haskell.service.ts @@ -1,4 +1,5 @@ import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; import { Http, Response, Headers, RequestOptions, URLSearchParams } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import { Archivo, Evaluacion } from '../objects/archivo'; @@ -18,7 +19,7 @@ export class HaskellService { * @param {Http} http - The injected Http. * @constructor */ - constructor(private http: Http, private authService: AuthenticationService) {} + constructor(private http: Http, private router: Router, private authService: AuthenticationService) {} getArchivos(cedula:string): Observable<Archivo[]> { let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization':'Bearer '+this.authService.getToken() }); @@ -109,6 +110,9 @@ export class HaskellService { * Handle HTTP error */ private handleError (error: any) { + if(error.status == 401){ + this.router.navigate(['/login']); + } // In a real world app, we might use a remote logging infrastructure // We'd also dig deeper into the error to get a better message let errMsg = (error.message) ? error.message : diff --git a/Frontend Angular 4/src/app/shared/services/usuario.service.ts b/Frontend Angular 4/src/app/shared/services/usuario.service.ts index 1cce787b26cf12c698616c0a161a84f8e2fb102d..3c65e92f443e5864d0700db499102f66f2ce2f89 100644 --- a/Frontend Angular 4/src/app/shared/services/usuario.service.ts +++ b/Frontend Angular 4/src/app/shared/services/usuario.service.ts @@ -1,8 +1,10 @@ import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; import { Http, Headers, Response, RequestOptions } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import { Usuario, Configuracion } from '../objects/usuario'; +import { AuthenticationService } from './authentication.service'; import 'rxjs/add/operator/map' import 'rxjs/add/operator/catch' @@ -10,10 +12,10 @@ import { SERVER } from '../config'; @Injectable() export class UsuarioService { - constructor(private http: Http) {} + constructor(private http: Http, private router: Router, private authService: AuthenticationService) {} actualizarConfiguracion(cedula: string, config: Configuracion) { - let headers = new Headers({ 'Content-Type': 'application/json' }); + let headers = new Headers({ 'Content-Type': 'application/json', 'Authorization':'Bearer '+this.authService.getToken() }); let options = new RequestOptions({ headers: headers }); return this.http.put(SERVER + '/servicios/usuario/' + cedula + "/configuracion", config, options) .map(this.extractData) @@ -26,6 +28,9 @@ export class UsuarioService { } private handleError(error: any) { + if(error.status == 401){ + this.router.navigate(['/login']); + } let errMsg = (error.message) ? error.message : error.status ? `${error.status} - ${error.statusText}` : 'Server error'; console.error(errMsg); // log to console instead diff --git a/Servidor JEE/src/main/java/edu/proygrado/ejb/CommandsBean.java b/Servidor JEE/src/main/java/edu/proygrado/ejb/CommandsBean.java index b9d2c7c1faf1eb4e7da47b91957d19e7c34cbc23..6f01fb8a3789c5b2c75dbee2c633e401c94b891c 100644 --- a/Servidor JEE/src/main/java/edu/proygrado/ejb/CommandsBean.java +++ b/Servidor JEE/src/main/java/edu/proygrado/ejb/CommandsBean.java @@ -24,6 +24,8 @@ import javax.json.JsonObject; import javax.json.JsonReader; import javax.json.JsonValue; import javax.servlet.ServletContext; +import javax.websocket.CloseReason; +import javax.websocket.CloseReason.CloseCodes; import javax.websocket.Session; import edu.proygrado.dto.ArchivoDTO; @@ -41,6 +43,9 @@ public class CommandsBean { @Inject UsuarioEJB usuarioEJB; + + @Inject + LoginEJB loginEJB; @Inject private ServletContext context; @@ -80,16 +85,24 @@ public class CommandsBean { restartProcess(this.cedula, token, session); System.err.println("Se reinicia el proceso."); } - + + if(!loginEJB.validarSesion(token)){ + session.close(new CloseReason(CloseCodes.VIOLATED_POLICY,"Sin permisos")); + System.out.print("Web socket finalizado"); + return; + } + if (comandoJson.containsKey("ping")) { // System.out.println(comandoJson.getString("ping")); } else if (comandoJson.containsKey("comando")) { + loginEJB.extendSession(token); String comando = comandoJson.getString("comando"); this.p_stdin.write(comando); this.p_stdin.newLine(); this.p_stdin.flush(); } else if (comandoJson.containsKey("load")) { + loginEJB.extendSession(token); int fileId = comandoJson.getInt("load"); JsonArray dependenciasJsonArray = comandoJson.getJsonArray("dependencias"); @@ -150,6 +163,7 @@ public class CommandsBean { Logger.getLogger(CommandsBean.class.getName()).log(Level.SEVERE, null, ex); } } else if (comandoJson.containsKey("restart")) { + //La extension de la sesion se realiza en restartProcess. restartProcess(this.cedula, token, session); } @@ -171,6 +185,12 @@ public class CommandsBean { public void restartProcess(String cedula, String token, Session session) throws InterruptedException { try { + if(!loginEJB.validarSesion(token)){ + session.close(new CloseReason(CloseCodes.VIOLATED_POLICY,"Sin permisos")); + System.out.print("Web socket finalizado"); + return; + } + loginEJB.extendSession(token); this.callback = session; this.cedula = cedula; if (this.proceso != null && this.proceso.isAlive()) { @@ -239,9 +259,13 @@ public class CommandsBean { directory = new File(fullPathMatefunTmp + this.cedula); } deleteDirectory(directory); - this.proceso.destroy(); - this.standardConsoleThread.interrupt(); - this.errorConsoleThread.interrupt(); + if(this.proceso!=null && this.proceso.isAlive()) + this.proceso.destroy(); + if(this.standardConsoleThread != null && this.standardConsoleThread.isAlive()) + this.standardConsoleThread.interrupt(); + if(this.errorConsoleThread != null && this.errorConsoleThread.isAlive()) + this.errorConsoleThread.interrupt(); + loginEJB.deleteExpiredSessions(); } private boolean deleteDirectory(File directory) { diff --git a/Servidor JEE/src/main/java/edu/proygrado/ejb/LoginEJB.java b/Servidor JEE/src/main/java/edu/proygrado/ejb/LoginEJB.java index b5afe2eb70723cafe360d2896610d8df34d81db6..ab747f39a80d36f60f5c7c1dfe05b19847903bba 100644 --- a/Servidor JEE/src/main/java/edu/proygrado/ejb/LoginEJB.java +++ b/Servidor JEE/src/main/java/edu/proygrado/ejb/LoginEJB.java @@ -10,6 +10,7 @@ import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Properties; @@ -41,6 +42,7 @@ import edu.proygrado.modelo.Grupo; import edu.proygrado.modelo.GrupoPK; import edu.proygrado.modelo.Liceo; import edu.proygrado.modelo.LiceoPK; +import edu.proygrado.modelo.Sesion; import edu.proygrado.modelo.Usuario; /** @@ -62,6 +64,48 @@ public class LoginEJB { @EJB private InvitadoEJB invitadoEJB; + public boolean validarSesion(String token){ + Sesion sesion = em.find(Sesion.class,token); + Date now = new Date(); + if(sesion != null && sesion.getTimestamp().getTime() > now.getTime()-60*60*1000){ + return true; + } + return false; + } + + private void updateSession(String token,Usuario usuario){ + Sesion sesion = em.find(Sesion.class,token); + if(sesion == null){ + sesion = new Sesion(); + sesion.setToken(token); + sesion.setUsuario(usuario); + sesion.setTimestamp(new Date()); + em.persist(sesion); + }else{ + sesion.setTimestamp(new Date()); + } + } + + public void extendSession(String token){ + Sesion sesion = em.find(Sesion.class,token); + if(sesion != null){ + sesion.setTimestamp(new Date()); + } + } + + public void deleteExpiredSessions(){ + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + cal.add(Calendar.HOUR_OF_DAY, -1); + Date horaExpiracion = cal.getTime(); + List<Sesion> sesiones = em.createQuery("select s from Sesion s where s.timestamp < :horaExpiracion", Sesion.class) + .setParameter("horaExpiracion", horaExpiracion) + .getResultList(); + for(Sesion sesion:sesiones){ + em.remove(sesion); + } + } + public UsuarioDTO login(String cedula, String password) throws MatefunException { Usuario usuario; @@ -78,6 +122,7 @@ public class LoginEJB { System.out.println("Retorno usuario local"); System.out.println(this.toString()); String tokenAuth = generateToken(); + updateSession(tokenAuth, usuario); invitadoEJB.setUsuario(tokenAuth, usuario); return new UsuarioDTO(tokenAuth, usuario); } @@ -98,6 +143,7 @@ public class LoginEJB { System.out.println("Credenciales locales invalidas pero validas en moodle. Retorno usuario."); usuario.setPassword(generateHash(password)); String tokenAuth = generateToken(); + updateSession(tokenAuth, usuario); invitadoEJB.setUsuario(tokenAuth, usuario); return new UsuarioDTO(tokenAuth, usuario); } else { @@ -180,6 +226,7 @@ public class LoginEJB { em.persist(root); em.persist(nuevoDesdeMoodle); String tokenAuth = generateToken(); + updateSession(tokenAuth, usuario); invitadoEJB.setUsuario(tokenAuth, nuevoDesdeMoodle); return new UsuarioDTO(tokenAuth, nuevoDesdeMoodle); } diff --git a/Servidor JEE/src/main/java/edu/proygrado/ejb/Sesion.java b/Servidor JEE/src/main/java/edu/proygrado/ejb/Sesion.java deleted file mode 100644 index 4b232af985f5f56b40a0fed168aca2158ae697c6..0000000000000000000000000000000000000000 --- a/Servidor JEE/src/main/java/edu/proygrado/ejb/Sesion.java +++ /dev/null @@ -1,23 +0,0 @@ -package edu.proygrado.ejb; - -import java.io.Serializable; - -import edu.proygrado.modelo.Usuario; - -public class Sesion implements Serializable{ - - /** - * - */ - private static final long serialVersionUID = 1L; - private Usuario usuario; - - public Usuario getUsuario() { - return usuario; - } - - public void setUsuario(Usuario usuario) { - this.usuario = usuario; - } - -} diff --git a/Servidor JEE/src/main/java/edu/proygrado/matefun/AuthenticationFilter.java b/Servidor JEE/src/main/java/edu/proygrado/matefun/AuthenticationFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..e8eb3aeca897a8580602a5ef1ac5a1495b7a2458 --- /dev/null +++ b/Servidor JEE/src/main/java/edu/proygrado/matefun/AuthenticationFilter.java @@ -0,0 +1,57 @@ +package edu.proygrado.matefun; + +import java.io.IOException; + +import javax.annotation.Priority; +import javax.inject.Inject; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.ResourceInfo; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.Provider; + +import edu.proygrado.ejb.LoginEJB; + +@Provider +@Priority(Priorities.AUTHENTICATION) +public class AuthenticationFilter implements ContainerRequestFilter { + + @Inject + LoginEJB login; + + @Override + public void filter(ContainerRequestContext context) throws IOException { + String path = context.getUriInfo().getPath(); + if(path.equals("/login")){ + return; + } + + String authorizationHeader = context.getHeaderString(HttpHeaders.AUTHORIZATION); + + // Chequear existencia de cabezal + if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) { + abort(context, Response.Status.UNAUTHORIZED); + return; + } + + // Extrae token de HTTP Authorization header + String token = authorizationHeader.substring("Bearer ".length()).trim(); + + if(!login.validarSesion(token)){ + abort(context, Response.Status.UNAUTHORIZED); + }else{ + login.extendSession(token); + } + + } + + private void abort(ContainerRequestContext context, Response.Status statusCode ) { + context.abortWith(Response.status(statusCode).type(MediaType.TEXT_PLAIN) + .entity("Error de seguridad").build()); + } + +} \ No newline at end of file diff --git a/Servidor JEE/src/main/java/edu/proygrado/modelo/Sesion.java b/Servidor JEE/src/main/java/edu/proygrado/modelo/Sesion.java new file mode 100644 index 0000000000000000000000000000000000000000..19d56752242e074f9377e6e9e0b5c8e4bf65e01b --- /dev/null +++ b/Servidor JEE/src/main/java/edu/proygrado/modelo/Sesion.java @@ -0,0 +1,41 @@ +package edu.proygrado.modelo; + + +import java.util.Date; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +@Entity +public class Sesion { + + @Id + private String token; + @Temporal(value = TemporalType.TIMESTAMP) + private Date timestamp; + @ManyToOne + private Usuario usuario; + + public String getToken() { + return token; + } + public void setToken(String token) { + this.token = token; + } + public Date getTimestamp() { + return timestamp; + } + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + public Usuario getUsuario() { + return usuario; + } + public void setUsuario(Usuario usuario) { + this.usuario = usuario; + } + +} diff --git a/Servidor JEE/src/main/resources/META-INF/persistence.xml b/Servidor JEE/src/main/resources/META-INF/persistence.xml index 2eb2226f5dd2d74649816e3979e32f480cbaf6c4..def388faad41ec461bf1a04851ccdf78b4a5d107 100644 --- a/Servidor JEE/src/main/resources/META-INF/persistence.xml +++ b/Servidor JEE/src/main/resources/META-INF/persistence.xml @@ -16,8 +16,10 @@ <class>edu.proygrado.modelo.Liceo</class> <class>edu.proygrado.modelo.LiceoPK</class> <class>edu.proygrado.modelo.Usuario</class> + <class>edu.proygrado.modelo.Sesion</class> <properties> <property name="javax.persistence.schema-generation.database.action" value="none" /> + <property name="hibernate.hbm2ddl.auto" value="update"/> </properties> </persistence-unit>