@ -0,0 +1,288 @@ | |||||
package mx.gob.jumapacelaya.api; | |||||
import com.nimbusds.jose.shaded.gson.*; | |||||
import mx.gob.jumapacelaya.models.RedmineUser; | |||||
import mx.gob.jumapacelaya.models.Ticket; | |||||
import org.springframework.beans.factory.annotation.Value; | |||||
import org.springframework.stereotype.Component; | |||||
import java.io.IOException; | |||||
import java.net.URI; | |||||
import java.net.http.HttpClient; | |||||
import java.net.http.HttpRequest; | |||||
import java.net.http.HttpResponse; | |||||
import java.time.LocalDate; | |||||
import java.time.format.DateTimeFormatter; | |||||
import java.time.format.DateTimeParseException; | |||||
import java.util.ArrayList; | |||||
import java.util.HashMap; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
@Component | |||||
public class RedmineClient { | |||||
private static final int PAGE_SIZE = 25; | |||||
static String REDMINE_URL; | |||||
static String API_KEY; | |||||
public static final Gson GSON = new Gson(); | |||||
public RedmineClient(@Value("${redmine.url}") String redmineUrl, @Value("${redmine.api_key}") String apiKey) { | |||||
REDMINE_URL = redmineUrl; | |||||
API_KEY = apiKey; | |||||
} | |||||
//AQUI OBTENGO LOS TICKETS DESDE REDMINE | |||||
public List<Ticket> getTickets(RedmineUser user, boolean includeClosed) { | |||||
List<Ticket> tickets = new ArrayList<>(); | |||||
HttpClient client = HttpClient.newHttpClient(); | |||||
int offset = 0; | |||||
// Si includeClose es true, incluira todos los tikets si no, incluira solo los que estan abiertos | |||||
String statusFilter = includeClosed ? "&status_id=*" : "&status_id=open"; | |||||
while (true) { | |||||
HttpRequest request = HttpRequest.newBuilder() | |||||
.uri(URI.create(REDMINE_URL + "/issues.json?key=" + user.getKey() + statusFilter + "&offset=" + offset)) | |||||
.header("Content-Type", "application/json") | |||||
.build(); | |||||
try { | |||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); | |||||
if (response.statusCode() == 200) { | |||||
String responseBody = response.body(); | |||||
List<Ticket> pageTickets = parseTickets(responseBody); | |||||
tickets.addAll(pageTickets); | |||||
if (pageTickets.size() < PAGE_SIZE) { | |||||
break; | |||||
} | |||||
offset += PAGE_SIZE; | |||||
} else { | |||||
System.err.println("Error en la respuesta: " + response.statusCode()); | |||||
break; | |||||
} | |||||
} catch (Exception e) { | |||||
e.printStackTrace(); | |||||
break; | |||||
} | |||||
} | |||||
System.out.println("Total tickets obtenidos: " + tickets.size()); | |||||
return tickets; | |||||
} | |||||
//AQUI OBTENGO LOS TICKETS DESDE REDMINE | |||||
public List<Ticket> getTicketsAuthor(RedmineUser user) { | |||||
List<Ticket> tickets = new ArrayList<>(); | |||||
HttpClient client = HttpClient.newHttpClient(); | |||||
int offset = 0; | |||||
while (true) { | |||||
HttpRequest request = HttpRequest.newBuilder() | |||||
.uri(URI.create(REDMINE_URL + "/issues.json?key=" + user.getKey() + "&author_id=" + user.getId() + "&offset=" + offset)) | |||||
.header("Content-Type", "application/json") | |||||
.build(); | |||||
try { | |||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); | |||||
if (response.statusCode() == 200) { | |||||
String responseBody = response.body(); | |||||
List<Ticket> pageTickets = parseTickets(responseBody); | |||||
tickets.addAll(pageTickets); | |||||
if (pageTickets.size() < PAGE_SIZE) { | |||||
break; | |||||
} | |||||
offset += PAGE_SIZE; | |||||
} else { | |||||
System.err.println("Error en la respuesta: " + response.statusCode()); | |||||
break; | |||||
} | |||||
} catch (Exception e) { | |||||
e.printStackTrace(); | |||||
break; | |||||
} | |||||
} | |||||
System.out.println("Total tickets obtenidos: " + tickets.size()); | |||||
return tickets; | |||||
} | |||||
// Aquí se parsean todos los tickets que existen y se les da un formato con los campos a mostrarse | |||||
private List<Ticket> parseTickets(String json) { | |||||
List<Ticket> tickets = new ArrayList<>(); | |||||
try { | |||||
JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); | |||||
JsonArray issues = jsonObject.getAsJsonArray("issues"); | |||||
if (issues != null) { | |||||
for (JsonElement issueElement : issues) { | |||||
JsonObject issue = issueElement.getAsJsonObject(); | |||||
// Verifica y obtiene el ID | |||||
int id = issue.has("id") && !issue.get("id").isJsonNull() ? issue.get("id").getAsInt() : 0; | |||||
// Verifica y obtiene el subject | |||||
String subject = issue.has("subject") && !issue.get("subject").isJsonNull() ? issue.get("subject").getAsString() : ""; | |||||
// Verifica y obtiene la descripción | |||||
String description = issue.has("description") && !issue.get("description").isJsonNull() ? issue.get("description").getAsString() : ""; | |||||
// Verifica y obtiene el status | |||||
String status = "Unknown"; | |||||
if (issue.has("status") && !issue.get("status").isJsonNull()) { | |||||
JsonObject statusObject = issue.getAsJsonObject("status"); | |||||
if (statusObject.has("name") && !statusObject.get("name").isJsonNull()) { | |||||
status = statusObject.get("name").getAsString(); | |||||
} | |||||
} | |||||
// Verifica y obtiene la fecha de creación | |||||
String dateString = issue.has("created_on") && !issue.get("created_on").isJsonNull() ? issue.get("created_on").getAsString() : ""; | |||||
LocalDate date = null; | |||||
if (!dateString.isEmpty()) { | |||||
try { | |||||
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME; | |||||
date = LocalDate.parse(dateString, formatter); | |||||
} catch (DateTimeParseException e) { | |||||
System.err.println("Error al parsear la fecha: " + dateString); | |||||
e.printStackTrace(); | |||||
} | |||||
} | |||||
//Verifica y obtiene el ID del tipo de ticket | |||||
Integer trackerId = null; | |||||
if (issue.has("tracker") && !issue.get("tracker").isJsonNull()) { | |||||
JsonObject trackerObject = issue.getAsJsonObject("tracker"); | |||||
if (trackerObject.has("id") && !trackerObject.get("id").isJsonNull()) { | |||||
trackerId = trackerObject.get("id").getAsInt(); | |||||
} | |||||
} | |||||
// Agrega el ticket a la lista | |||||
tickets.add(new Ticket(id, subject, description, status, date != null ? date.toString() : "", trackerId)); | |||||
} | |||||
} else { | |||||
System.out.println("La respuesta JSON no contiene la clave 'issues'"); | |||||
} | |||||
} catch (Exception e) { | |||||
e.printStackTrace(); | |||||
System.out.println("Ocurrió un error al parsear los tickets: " + e.getMessage()); | |||||
} | |||||
return tickets; | |||||
} | |||||
public RedmineUser getMyAccount(String username) { | |||||
HttpRequest request = HttpRequest.newBuilder() | |||||
.uri(URI.create(REDMINE_URL + "/my/account.json")) | |||||
.header("Content-Type", "application/json") | |||||
.header("X-Redmine-Switch-User", username) | |||||
.header("X-Redmine-API-Key", API_KEY) | |||||
.GET() | |||||
.build(); | |||||
try { | |||||
HttpClient client = HttpClient.newHttpClient(); | |||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); | |||||
JsonObject jsonObject = JsonParser.parseString(response.body()).getAsJsonObject(); | |||||
JsonObject userJson = jsonObject.get("user").getAsJsonObject(); | |||||
RedmineUser user = new RedmineUser(); | |||||
user.setId(userJson.get("id").getAsInt()); | |||||
user.setLogin(userJson.get("login").getAsString()); | |||||
user.setFirstname(userJson.get("firstname").getAsString()); | |||||
user.setLastname(userJson.get("lastname").getAsString()); | |||||
user.setMail(userJson.get("mail").getAsString()); | |||||
user.setKey(userJson.get("api_key").getAsString()); | |||||
return user; | |||||
//return response.body(); | |||||
} catch (IOException | InterruptedException e) { | |||||
e.printStackTrace(); | |||||
return null; | |||||
} | |||||
} | |||||
public static RedmineUser createRedmineUser(String username, String firstname, String lastname, String mail) { | |||||
HttpClient client = HttpClient.newHttpClient(); | |||||
Map<String, Object> user = new HashMap<>(); | |||||
user.put("login", username); | |||||
user.put("firstname", firstname); | |||||
user.put("lastname", lastname); | |||||
user.put("mail", mail); | |||||
user.put("auth_source_id", "1"); | |||||
Map<String, Object> payload = new HashMap<>(); | |||||
payload.put("user", user); | |||||
String jsonPayload = new Gson().toJson(payload); | |||||
HttpRequest request = HttpRequest.newBuilder() | |||||
.uri(URI.create(REDMINE_URL + "/users.json")) | |||||
.header("Content-Type", "application/json") | |||||
.header("X-Redmine-API-Key", API_KEY) | |||||
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) | |||||
.build(); | |||||
try { | |||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); | |||||
JsonObject jsonObject = JsonParser.parseString(response.body()).getAsJsonObject(); | |||||
JsonObject userJson = jsonObject.get("user").getAsJsonObject(); | |||||
RedmineUser newUser = new RedmineUser(); | |||||
newUser.setId(userJson.get("id").getAsInt()); | |||||
newUser.setLogin(userJson.get("login").getAsString()); | |||||
newUser.setFirstname(userJson.get("firstname").getAsString()); | |||||
newUser.setLastname(userJson.get("lastname").getAsString()); | |||||
newUser.setMail(userJson.get("mail").getAsString()); | |||||
return newUser; | |||||
} catch (Exception e) { | |||||
e.printStackTrace(); | |||||
return null; | |||||
} | |||||
} | |||||
//AQUI OBTENGO A LOS USUARIOS | |||||
//Se devuelven en formato Json | |||||
public RedmineUser getUserByUsername(String username) { | |||||
HttpClient client = HttpClient.newHttpClient(); | |||||
HttpRequest request = HttpRequest.newBuilder() | |||||
.uri(URI.create(REDMINE_URL + "/users.json?name=" + username )) | |||||
.header("Content-Type", "application/json") | |||||
.header("X-Redmine-API-Key", API_KEY) | |||||
.build(); | |||||
try { | |||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); | |||||
if (response.statusCode() == 200) { | |||||
String responseBody = response.body(); | |||||
System.out.println("Datos del usuario " + responseBody); | |||||
return parseUser(responseBody); | |||||
} else { | |||||
System.err.println("Error en la respuesta: " + response.statusCode()); | |||||
} | |||||
} catch (Exception e) { | |||||
e.printStackTrace(); | |||||
} | |||||
return null; | |||||
} | |||||
//Aqui se parsean a los usuarios y se les da un formato con los campos a mostrarse | |||||
private RedmineUser parseUser(String json) { | |||||
JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); | |||||
JsonObject userJson = jsonObject.getAsJsonArray("users").get(0).getAsJsonObject(); | |||||
RedmineUser user = new RedmineUser(); | |||||
user.setId(userJson.get("id").getAsInt()); | |||||
user.setLogin(userJson.get("login").getAsString()); | |||||
user.setFirstname(userJson.get("firstname").getAsString()); | |||||
user.setLastname(userJson.get("lastname").getAsString()); | |||||
user.setMail(userJson.get("mail").getAsString()); | |||||
//user.setKey(userJson.get("key").getAsString()); | |||||
return user; | |||||
} | |||||
} |
@ -0,0 +1 @@ | |||||
package mx.gob.jumapacelaya.api; |
@ -0,0 +1,13 @@ | |||||
package mx.gob.jumapacelaya.api; | |||||
import org.springframework.beans.factory.annotation.Value; | |||||
import org.springframework.stereotype.Component; | |||||
@Component | |||||
public class ServerProperties { | |||||
@Value("${redmine.url}") | |||||
public String REDMINE_URL; | |||||
@Value("${redmine.api_key}") | |||||
public String API_KEY; | |||||
} |
@ -0,0 +1,84 @@ | |||||
package mx.gob.jumapacelaya.models; | |||||
import org.springframework.security.core.GrantedAuthority; | |||||
import org.springframework.security.core.userdetails.UserDetails; | |||||
import java.util.Collection; | |||||
public class CustomUserDetails implements UserDetails { | |||||
private String username; | |||||
private String firstName; | |||||
private String lastName; | |||||
private String email; | |||||
private Collection<? extends GrantedAuthority> authorities; | |||||
private String password; | |||||
public CustomUserDetails(String username, String firstName, String lastName, String email, Collection<? extends GrantedAuthority> authorities, String password) { | |||||
this.username = username; | |||||
this.firstName = firstName; | |||||
this.lastName = lastName; | |||||
this.email = email; | |||||
this.authorities = authorities; | |||||
this.password = password; | |||||
} | |||||
// Getters and setters | |||||
public String getFirstName() { | |||||
return firstName; | |||||
} | |||||
public void setFirstName(String firstName) { | |||||
this.firstName = firstName; | |||||
} | |||||
public String getLastName() { | |||||
return lastName; | |||||
} | |||||
public void setLastName(String lastName) { | |||||
this.lastName = lastName; | |||||
} | |||||
public String getEmail() { | |||||
return email; | |||||
} | |||||
public void setEmail(String email) { | |||||
this.email = email; | |||||
} | |||||
@Override | |||||
public Collection<? extends GrantedAuthority> getAuthorities() { | |||||
return authorities; | |||||
} | |||||
@Override | |||||
public String getPassword() { | |||||
return password; | |||||
} | |||||
@Override | |||||
public String getUsername() { | |||||
return username; | |||||
} | |||||
@Override | |||||
public boolean isAccountNonExpired() { | |||||
return true; | |||||
} | |||||
@Override | |||||
public boolean isAccountNonLocked() { | |||||
return true; | |||||
} | |||||
@Override | |||||
public boolean isCredentialsNonExpired() { | |||||
return true; | |||||
} | |||||
@Override | |||||
public boolean isEnabled() { | |||||
return true; | |||||
} | |||||
} |
@ -0,0 +1,72 @@ | |||||
package mx.gob.jumapacelaya.models; | |||||
public class RedmineUser { | |||||
private int id; | |||||
private String login; | |||||
private String firstname; | |||||
private String lastname; | |||||
private String mail; | |||||
private String key; | |||||
public int getId() { | |||||
return id; | |||||
} | |||||
public void setId(int id) { | |||||
this.id = id; | |||||
} | |||||
public String getLogin() { | |||||
return login; | |||||
} | |||||
public void setLogin(String login) { | |||||
this.login = login; | |||||
} | |||||
public String getFirstname() { | |||||
return firstname; | |||||
} | |||||
public void setFirstname(String firstname) { | |||||
this.firstname = firstname; | |||||
} | |||||
public String getLastname() { | |||||
return lastname; | |||||
} | |||||
public void setLastname(String lastname) { | |||||
this.lastname = lastname; | |||||
} | |||||
public String getMail() { | |||||
return mail; | |||||
} | |||||
public void setMail(String mail) { | |||||
this.mail = mail; | |||||
} | |||||
public String getKey() { | |||||
return key; | |||||
} | |||||
public void setKey(String key) { | |||||
this.key = key; | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return "RedmineUser{" + | |||||
"id=" + id + | |||||
", login='" + login + '\'' + | |||||
", firstname='" + firstname + '\'' + | |||||
", lastname='" + lastname + '\'' + | |||||
", mail='" + mail + '\'' + | |||||
", key='" + key + '\'' + | |||||
'}'; | |||||
} | |||||
} |
@ -0,0 +1,98 @@ | |||||
package mx.gob.jumapacelaya.models; | |||||
import java.sql.Date; | |||||
import java.time.LocalDate; | |||||
public class Ticket { | |||||
private int id; | |||||
private String subject; | |||||
private String description; | |||||
private String status; | |||||
private LocalDate dateCreate; | |||||
private User author; | |||||
private Integer trackerId; | |||||
public Ticket(int id, String subject, String description, String status, String dateCreate, Integer trackerId) { | |||||
this.id = id; | |||||
this.subject = subject; | |||||
this.description = description; | |||||
this.status = status; | |||||
this.dateCreate = LocalDate.parse(dateCreate); | |||||
this.author = author; | |||||
this.trackerId = trackerId; | |||||
} | |||||
public int getId() { | |||||
return id; | |||||
} | |||||
public String getSubject() { | |||||
return subject; | |||||
} | |||||
public String getDescription() { | |||||
return description; | |||||
} | |||||
public String getStatus() { | |||||
return status; | |||||
} | |||||
public User getAuthor() { | |||||
return author; | |||||
} | |||||
public void setAuthor(User author) { | |||||
this.author = author; | |||||
} | |||||
public Date getDateCreate() { | |||||
return java.sql.Date.valueOf(this.dateCreate); | |||||
} | |||||
public Integer getTrackerId() { | |||||
return trackerId; | |||||
} | |||||
public void setTrackerId(Integer tipoId) { | |||||
this.trackerId = trackerId; | |||||
} | |||||
public static class User { | |||||
private String username; | |||||
public User(String username) { | |||||
this.username = username; | |||||
} | |||||
public String getUsername() { | |||||
return username; | |||||
} | |||||
public void setUsername(String username) { | |||||
this.username = username; | |||||
} | |||||
} | |||||
public String tiempoEst(Integer trackerId) { | |||||
if (trackerId == null) { | |||||
return "Desconocido"; | |||||
} | |||||
switch (trackerId) { | |||||
case 5,16,17: | |||||
return "2 horas max."; | |||||
case 4,13,14: | |||||
return "2 a 6 horas"; | |||||
case 6,7,9,10,11,15: | |||||
return "1 a 2 Dias"; | |||||
default: | |||||
return "N/A"; | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,46 @@ | |||||
package mx.gob.jumapacelaya.services; | |||||
import mx.gob.jumapacelaya.models.CustomUserDetails; | |||||
import org.springframework.ldap.core.LdapTemplate; | |||||
import org.springframework.ldap.filter.EqualsFilter; | |||||
import org.springframework.stereotype.Service; | |||||
import javax.naming.NamingException; | |||||
import javax.naming.directory.Attributes; | |||||
import javax.naming.directory.SearchControls; | |||||
import java.util.List; | |||||
@Service | |||||
public class LdapService { | |||||
private final LdapTemplate ldapTemplate; | |||||
public LdapService(LdapTemplate ldapTemplate) { | |||||
this.ldapTemplate = ldapTemplate; | |||||
} | |||||
public CustomUserDetails getUserDetails(String username) { | |||||
EqualsFilter filter = new EqualsFilter("sAMAccountName", username); | |||||
SearchControls searchControls = new SearchControls(); | |||||
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); | |||||
searchControls.setReturningAttributes(new String[]{"givenName", "sn", "mail"}); | |||||
searchControls.setReturningObjFlag(true); | |||||
List<CustomUserDetails> result = ldapTemplate.search("", filter.encode(), searchControls, (Attributes attrs) -> { | |||||
String firstName = getAttribute(attrs, "givenName"); | |||||
String lastName = getAttribute(attrs, "sn"); | |||||
String email = getAttribute(attrs, "mail"); | |||||
return new CustomUserDetails(username, firstName, lastName, email, null, null); // Ajustar los últimos dos parámetros si es necesario | |||||
}); | |||||
return result.isEmpty() ? null : result.get(0); | |||||
} | |||||
private String getAttribute(Attributes attributes, String attributeName) { | |||||
try { | |||||
return attributes.get(attributeName) != null ? attributes.get(attributeName).get().toString(): null; | |||||
} catch (NamingException e) { | |||||
return null; | |||||
} | |||||
} | |||||
} |
@ -1,8 +1,89 @@ | |||||
package mx.gob.jumapacelaya.services; | package mx.gob.jumapacelaya.services; | ||||
import com.vaadin.flow.server.VaadinService; | |||||
import mx.gob.jumapacelaya.api.RedmineClient; | |||||
import mx.gob.jumapacelaya.services.SecurityService; | |||||
import mx.gob.jumapacelaya.models.CustomUserDetails; | |||||
import mx.gob.jumapacelaya.models.RedmineUser; | |||||
import org.slf4j.Logger; | |||||
import org.slf4j.LoggerFactory; | |||||
import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||
@Service | @Service | ||||
public class UserService { | public class UserService { | ||||
private final SecurityService securityService; | |||||
private final RedmineClient redmineClient; | |||||
private static final Logger logger = LoggerFactory.getLogger(UserService.class); | |||||
private final LdapService ldpaService; | |||||
public UserService(SecurityService securityService, RedmineClient redmineClient, LdapService ldpaService) { | |||||
this.securityService = securityService; | |||||
this.redmineClient = redmineClient; | |||||
this.ldpaService = ldpaService; | |||||
} | |||||
public RedmineUser getAuthenticatedRedmineUser() { | |||||
try { | |||||
String username = securityService.getAuthenticatedUser(); | |||||
logger.info("Usuario autenticado: " + username); | |||||
if (username != null) { | |||||
RedmineUser user = redmineClient.getUserByUsername(username); | |||||
if (user == null) { | |||||
CustomUserDetails userDetails = ldpaService.getUserDetails(username); | |||||
if (userDetails != null) { | |||||
if (userDetails.getEmail() == null || userDetails.getEmail().isEmpty()) { | |||||
logger.error("El usuario: " + username + " no tiene correo electronico."); | |||||
return null; | |||||
} | |||||
RedmineUser newUser = RedmineClient.createRedmineUser( | |||||
username, | |||||
userDetails.getFirstName(), | |||||
userDetails.getLastName(), | |||||
userDetails.getEmail() | |||||
); | |||||
if (newUser != null) { | |||||
logger.info("Usuario creado en Redmine: " + newUser); | |||||
return newUser; | |||||
} else { | |||||
logger.error("Error al crear el usuario en Redmine"); | |||||
} | |||||
} else { | |||||
logger.error("No se encontraron detalles del usuario en LDAP"); | |||||
} | |||||
} else { | |||||
logger.info("Usuario autenticado en Redmine: " + user); | |||||
} | |||||
return user; | |||||
} | |||||
} catch (Exception e) { | |||||
logger.error("Error al obtener al usuario autenticado en Redmine", e); | |||||
} | |||||
return null; | |||||
} | |||||
public RedmineUser getRedmineUser() { | |||||
RedmineUser userclient = (RedmineUser) VaadinService.getCurrentRequest().getWrappedSession().getAttribute("myaccount"); | |||||
if (userclient == null) { | |||||
RedmineUser user = getAuthenticatedRedmineUser(); | |||||
if (user != null) { | |||||
RedmineUser myAccount = redmineClient.getMyAccount(user.getLogin()); | |||||
if (myAccount != null && !myAccount.getKey().isEmpty()) { | |||||
userclient = myAccount; | |||||
VaadinService.getCurrentRequest().getWrappedSession().setAttribute("myaccount", myAccount); | |||||
} else { | |||||
// Crear un nuevo usuario si no existe | |||||
myAccount = RedmineClient.createRedmineUser(user.getLogin(), user.getFirstname(), user.getLastname(), user.getMail()); | |||||
if (myAccount != null && !myAccount.getKey().isEmpty()) { | |||||
userclient = myAccount; | |||||
VaadinService.getCurrentRequest().getWrappedSession().setAttribute("myaccount", myAccount); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
return userclient; | |||||
} | |||||
} | } |