@ -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; | |||
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; | |||
@Service | |||
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; | |||
} | |||
} |