Refactor: reorganización completa del proyecto y documentación consolidada
Esta actualización reorganiza el proyecto de reverse engineering de la API de ADIF con los siguientes cambios: Estructura del proyecto: - Movida documentación principal a carpeta docs/ - Consolidados archivos markdown redundantes en CLAUDE.md (contexto completo del proyecto) - Organización de tests en carpeta tests/ con README explicativo - APK renombrado de base.apk a adif.apk para mayor claridad Archivos de código: - Movidos adif_auth.py y adif_client.py a la raíz (antes en api_testing_scripts/) - Eliminados scripts de testing obsoletos y scripts de Frida no utilizados - Nuevos tests detallados: test_endpoints_detailed.py y test_onepaths_with_real_trains.py Descubrimientos: - Documentados nuevos hallazgos en docs/NEW_DISCOVERIES.md - Actualización de onePaths funcionando con commercialNumber real (devuelve 200) - Extraídos 1587 códigos de estación en station_codes.txt Configuración: - Actualizado .gitignore con mejores patrones para Python e IDEs - Eliminados archivos temporales de depuración y logs
This commit is contained in:
508
docs/API_REQUEST_BODIES.md
Normal file
508
docs/API_REQUEST_BODIES.md
Normal file
@@ -0,0 +1,508 @@
|
||||
# Análisis de Request Bodies - API ADIF
|
||||
|
||||
> Ingeniería reversa del paquete `com.adif.elcanomovil.serviceNetworking`
|
||||
>
|
||||
> Fecha: 2025-12-04
|
||||
|
||||
## Tabla de Contenidos
|
||||
- [1. Headers de Autenticación](#1-headers-de-autenticación)
|
||||
- [2. Request Bodies](#2-request-bodies)
|
||||
- [3. Endpoints y URLs Base](#3-endpoints-y-urls-base)
|
||||
- [4. Configuración de Red](#4-configuración-de-red)
|
||||
- [5. Sistema de Autenticación](#5-sistema-de-autenticación)
|
||||
- [6. Referencias de Código](#6-referencias-de-código)
|
||||
|
||||
---
|
||||
|
||||
## 1. Headers de Autenticación
|
||||
|
||||
### 1.1 Headers Estáticos
|
||||
|
||||
**Archivo:** `ServicePaths.java:67-76`
|
||||
|
||||
#### Para Circulaciones
|
||||
```
|
||||
User-key: f4ce9fbfa9d721e39b8984805901b5df
|
||||
Content-Type: application/json;charset=utf-8
|
||||
```
|
||||
|
||||
#### Para Estaciones
|
||||
```
|
||||
User-key: 0d021447a2fd2ac64553674d5a0c1a6f
|
||||
Content-Type: application/json;charset=utf-8
|
||||
```
|
||||
|
||||
#### Para AVISA (Login/Refresh)
|
||||
```
|
||||
Authorization: Basic YXZpc3RhX2NsaWVudF9hbmRyb2lkOjh5WzZKNyFmSjwhXypmYXE1NyNnOSohNElwa2MjWC1BTg==
|
||||
```
|
||||
|
||||
**Decodificado (Base64):**
|
||||
```
|
||||
avista_client_android:8y[6J7!fJ<_*faq57#g9*!4Ipkc#X-AN
|
||||
```
|
||||
|
||||
### 1.2 Headers Dinámicos (Generados por AuthHeaderInterceptor)
|
||||
|
||||
**Archivo:** `AuthHeaderInterceptor.java:38-83`
|
||||
|
||||
La aplicación genera automáticamente estos headers adicionales:
|
||||
|
||||
```
|
||||
X-CanalMovil-Authentication: <token_generado>
|
||||
X-CanalMovil-deviceID: <device_id>
|
||||
X-CanalMovil-pushID: <push_id>
|
||||
```
|
||||
|
||||
**Algoritmo de generación:**
|
||||
El token se calcula usando la clase `ElcanoClientAuth` con:
|
||||
- Host del servidor
|
||||
- Path completo de la URL
|
||||
- Parámetros de query
|
||||
- Método HTTP (GET/POST)
|
||||
- Payload (body serializado sin espacios)
|
||||
- ID de usuario persistente
|
||||
- Cliente: "AndroidElcanoApp"
|
||||
|
||||
---
|
||||
|
||||
## 2. Request Bodies
|
||||
|
||||
### 2.1 Circulaciones - Salidas/Llegadas/Entre Estaciones
|
||||
|
||||
**Endpoints:**
|
||||
- `/portroyalmanager/secure/circulationpaths/departures/traffictype/`
|
||||
- `/portroyalmanager/secure/circulationpaths/arrivals/traffictype/`
|
||||
- `/portroyalmanager/secure/circulationpaths/betweenstations/traffictype/`
|
||||
|
||||
**Modelo:** `TrafficCirculationPathRequest`
|
||||
**Archivo:** `circulations/model/request/TrafficCirculationPathRequest.java:10-212`
|
||||
|
||||
```json
|
||||
{
|
||||
"commercialService": "YES|NOT|BOTH",
|
||||
"commercialStopType": "YES|NOT|BOTH",
|
||||
"destinationStationCode": "string o null",
|
||||
"originStationCode": "string o null",
|
||||
"page": {
|
||||
"pageNumber": 0
|
||||
},
|
||||
"stationCode": "string o null",
|
||||
"trafficType": "CERCANIAS|AVLDMD|OTHERS|TRAVELERS|GOODS|ALL"
|
||||
}
|
||||
```
|
||||
|
||||
#### Ejemplo Real
|
||||
```json
|
||||
{
|
||||
"commercialService": "BOTH",
|
||||
"commercialStopType": "BOTH",
|
||||
"destinationStationCode": null,
|
||||
"originStationCode": null,
|
||||
"page": {
|
||||
"pageNumber": 0
|
||||
},
|
||||
"stationCode": "60000",
|
||||
"trafficType": "ALL"
|
||||
}
|
||||
```
|
||||
|
||||
#### Valores Permitidos
|
||||
|
||||
**commercialService / commercialStopType** (`CirculationPathRequest.java:65-67`):
|
||||
- `YES` - Solo servicios/paradas comerciales
|
||||
- `NOT` - Sin servicios/paradas comerciales
|
||||
- `BOTH` - Todos los tipos
|
||||
|
||||
**trafficType** (`TrafficType.java:16-21`):
|
||||
- `CERCANIAS` - Trenes de cercanías
|
||||
- `AVLDMD` - Alta velocidad larga y media distancia
|
||||
- `OTHERS` - Otros tipos
|
||||
- `TRAVELERS` - Viajeros
|
||||
- `GOODS` - Mercancías
|
||||
- `ALL` - Todos los tipos
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Circulaciones - Rutas Específicas
|
||||
|
||||
**Endpoints:**
|
||||
- `/portroyalmanager/secure/circulationpathdetails/onepaths/`
|
||||
- `/portroyalmanager/secure/circulationpathdetails/severalpaths/`
|
||||
|
||||
**Modelo:** `OneOrSeveralPathsRequest`
|
||||
**Archivo:** `circulations/model/request/OneOrSeveralPathsRequest.java:11-140`
|
||||
|
||||
```json
|
||||
{
|
||||
"allControlPoints": true/false/null,
|
||||
"commercialNumber": "string o null",
|
||||
"destinationStationCode": "string o null",
|
||||
"launchingDate": 1733356800000,
|
||||
"originStationCode": "string o null"
|
||||
}
|
||||
```
|
||||
|
||||
#### Ejemplo Real
|
||||
```json
|
||||
{
|
||||
"allControlPoints": true,
|
||||
"commercialNumber": "04138",
|
||||
"destinationStationCode": "60000",
|
||||
"launchingDate": 1733356800000,
|
||||
"originStationCode": "71801"
|
||||
}
|
||||
```
|
||||
|
||||
**Notas importantes:**
|
||||
- `launchingDate` es un timestamp en **milisegundos** (tipo Long en Java)
|
||||
- `allControlPoints`: indica si se quieren todos los puntos de control de la ruta
|
||||
- Todos los campos son opcionales (pueden ser null)
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Composiciones de Trenes
|
||||
|
||||
**Endpoint:** `/portroyalmanager/secure/circulationpaths/compositions/path/`
|
||||
|
||||
**Modelo:** `OneOrSeveralPathsRequest` (mismo que rutas)
|
||||
**Archivo:** `compositions/CompositionsService.java:14-18`
|
||||
|
||||
```json
|
||||
{
|
||||
"allControlPoints": true/false/null,
|
||||
"commercialNumber": "string o null",
|
||||
"destinationStationCode": "string o null",
|
||||
"launchingDate": 1733356800000,
|
||||
"originStationCode": "string o null"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.4 Estaciones - Detalles de una Estación
|
||||
|
||||
**Endpoint:** `/portroyalmanager/secure/stations/onestation/`
|
||||
|
||||
**Modelo:** `OneStationRequest`
|
||||
**Archivo:** `stations/model/OneStationRequest.java:9-93`
|
||||
|
||||
```json
|
||||
{
|
||||
"detailedInfo": {
|
||||
"extendedStationInfo": true,
|
||||
"stationActivities": true,
|
||||
"stationBanner": true,
|
||||
"stationCommercialServices": true,
|
||||
"stationInfo": true,
|
||||
"stationServices": true,
|
||||
"stationTransportServices": true
|
||||
},
|
||||
"stationCode": "60000",
|
||||
"token": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**Notas importantes:**
|
||||
- El objeto `detailedInfo` controla qué información se devuelve en la respuesta
|
||||
- Todos los campos booleanos por defecto son `true` (ver `DetailedInfoDTO.java:149`)
|
||||
- El `token` es requerido
|
||||
|
||||
#### Campos de DetailedInfo
|
||||
|
||||
**Archivo:** `stations/model/DetailedInfoDTO.java:10-151`
|
||||
|
||||
| Campo | Tipo | Descripción |
|
||||
|-------|------|-------------|
|
||||
| `extendedStationInfo` | boolean | Información extendida de la estación |
|
||||
| `stationActivities` | boolean | Actividades de la estación |
|
||||
| `stationBanner` | boolean | Banner/anuncios de la estación |
|
||||
| `stationCommercialServices` | boolean | Servicios comerciales |
|
||||
| `stationInfo` | boolean | Información básica |
|
||||
| `stationServices` | boolean | Servicios disponibles |
|
||||
| `stationTransportServices` | boolean | Servicios de transporte |
|
||||
|
||||
---
|
||||
|
||||
### 2.5 Observaciones de Estaciones
|
||||
|
||||
**Endpoint:** `/portroyalmanager/secure/stationsobservations/`
|
||||
|
||||
**Modelo:** `StationObservationsRequest`
|
||||
**Archivo:** `stationObservations/model/StationObservationsRequest.java:10-53`
|
||||
|
||||
```json
|
||||
{
|
||||
"stationCodes": ["60000", "71801"]
|
||||
}
|
||||
```
|
||||
|
||||
#### Ejemplo Real
|
||||
```json
|
||||
{
|
||||
"stationCodes": ["60000", "71801", "79600"]
|
||||
}
|
||||
```
|
||||
|
||||
**Notas:**
|
||||
- Array de códigos de estación (strings)
|
||||
- Campo requerido
|
||||
- Puede contener múltiples códigos
|
||||
|
||||
---
|
||||
|
||||
## 3. Endpoints y URLs Base
|
||||
|
||||
### 3.1 URLs Base
|
||||
|
||||
**Archivo:** `di/NetworkModule.java:73-159`
|
||||
|
||||
| Servicio | URL Base | Autenticación |
|
||||
|----------|----------|---------------|
|
||||
| **Circulaciones** | `https://circulacion.api.adif.es` | Securizada (con AuthHeaderInterceptor) |
|
||||
| **Estaciones** | `https://estaciones.api.adif.es` | Securizada (con AuthHeaderInterceptor) |
|
||||
| **AVISA** | `https://avisa.adif.es` | Básica (sin AuthHeaderInterceptor) |
|
||||
| **Elcano Web** | `https://elcanoweb.adif.es/api/` | - |
|
||||
|
||||
### 3.2 Paths Completos - Estaciones
|
||||
|
||||
**Archivo:** `ServicePaths.java:106-112`
|
||||
|
||||
```
|
||||
GET /portroyalmanager/secure/stations/allstations/reducedinfo/{token}/
|
||||
POST /portroyalmanager/secure/stations/onestation/
|
||||
POST /portroyalmanager/secure/stationsobservations/
|
||||
```
|
||||
|
||||
### 3.3 Paths Completos - Circulaciones
|
||||
|
||||
**Archivo:** `ServicePaths.java:41-51`
|
||||
|
||||
```
|
||||
POST /portroyalmanager/secure/circulationpaths/departures/traffictype/
|
||||
POST /portroyalmanager/secure/circulationpaths/arrivals/traffictype/
|
||||
POST /portroyalmanager/secure/circulationpaths/betweenstations/traffictype/
|
||||
POST /portroyalmanager/secure/circulationpathdetails/onepaths/
|
||||
POST /portroyalmanager/secure/circulationpathdetails/severalpaths/
|
||||
```
|
||||
|
||||
### 3.4 Paths Completos - Composiciones
|
||||
|
||||
**Archivo:** `ServicePaths.java:55-61`
|
||||
|
||||
```
|
||||
POST /portroyalmanager/secure/circulationpaths/compositions/path/
|
||||
```
|
||||
|
||||
### 3.5 Paths Completos - AVISA
|
||||
|
||||
**Archivo:** `ServicePaths.java:82-92` y `ServicePaths.java:29-37`
|
||||
|
||||
```
|
||||
POST /avisa-ws/api/token (login)
|
||||
POST /avisa-ws/api/token (refresh)
|
||||
POST /avisa-ws/api/v1/client (register)
|
||||
GET /avisa-ws/api/v1/station (stations)
|
||||
GET /avisa-ws/api/v1/category (categories)
|
||||
GET /avisa-ws/api/v1/incidence (incidences list)
|
||||
GET /avisa-ws/api/v1/incidence/{id} (incidence details)
|
||||
POST /avisa-ws/api/v1/incidence (create incidence)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Configuración de Red
|
||||
|
||||
### 4.1 Configuración de OkHttpClient
|
||||
|
||||
**Archivo:** `di/NetworkModule.java:100-132`
|
||||
|
||||
#### Cliente Básico
|
||||
```kotlin
|
||||
OkHttpClient.Builder()
|
||||
.certificatePinner(certificatePinner)
|
||||
.connectTimeout(60, TimeUnit.SECONDS)
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.build()
|
||||
```
|
||||
|
||||
#### Cliente Securizado (con autenticación)
|
||||
```kotlin
|
||||
OkHttpClient.Builder()
|
||||
.addInterceptor(AuthHeaderInterceptor(userId))
|
||||
.certificatePinner(certificatePinner)
|
||||
.connectTimeout(60, TimeUnit.SECONDS)
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.build()
|
||||
```
|
||||
|
||||
**Timeouts:**
|
||||
- Connect timeout: 60 segundos
|
||||
- Read timeout: 60 segundos
|
||||
|
||||
### 4.2 Servicios que Usan Cliente Securizado
|
||||
|
||||
**Archivo:** `di/NetworkModule.java`
|
||||
|
||||
- `CirculationService` (línea 73)
|
||||
- `StationsService` (línea 142)
|
||||
- `StationObservationsService` (línea 135)
|
||||
- `CompositionsService` (línea 156)
|
||||
|
||||
### 4.3 Servicios que Usan Cliente Básico
|
||||
|
||||
- `AvisaLoginService` (línea 50)
|
||||
- `AvisaStationsService` (línea 57)
|
||||
- `IncidenceService` (línea 80)
|
||||
- `SubscriptionsService` (línea 149)
|
||||
|
||||
---
|
||||
|
||||
## 5. Sistema de Autenticación
|
||||
|
||||
### 5.1 AuthHeaderInterceptor
|
||||
|
||||
**Archivo:** `interceptors/AuthHeaderInterceptor.java:27-84`
|
||||
|
||||
Este interceptor se ejecuta en **todas** las peticiones de los servicios securizados.
|
||||
|
||||
#### Proceso de Autenticación
|
||||
|
||||
1. **Generación de User ID Persistente**
|
||||
- Usa `GeneratePersistentUserIdUseCase`
|
||||
- El ID se guarda y reutiliza entre sesiones
|
||||
|
||||
2. **Construcción del Token**
|
||||
```java
|
||||
ElcanoClientAuth.Builder()
|
||||
.host(request.url().host())
|
||||
.contentType("application/json;charset=utf-8")
|
||||
.path(request.url().encodedPath())
|
||||
.params(request.url().encodedQuery())
|
||||
.xElcanoClient("AndroidElcanoApp")
|
||||
.xElcanoUserId(userId)
|
||||
.httpMethodName(request.method())
|
||||
.payload(bodyJsonWithoutSpaces)
|
||||
.build()
|
||||
```
|
||||
|
||||
3. **Generación de Headers**
|
||||
- El objeto `ElcanoClientAuth` genera headers de autenticación
|
||||
- Se añaden automáticamente a la petición
|
||||
|
||||
#### Headers Generados
|
||||
|
||||
```
|
||||
X-CanalMovil-Authentication: <token_calculado>
|
||||
X-CanalMovil-deviceID: <device_id>
|
||||
X-CanalMovil-pushID: <push_id>
|
||||
```
|
||||
|
||||
### 5.2 Clase GetKeysHelper
|
||||
|
||||
**Archivo:** `AuthHeaderInterceptor.java:44`
|
||||
|
||||
Proporciona claves para la autenticación:
|
||||
- `getKeysHelper.a()` - Primera clave
|
||||
- `getKeysHelper.b()` - Segunda clave
|
||||
|
||||
Estas claves se usan en el algoritmo de firma/autenticación.
|
||||
|
||||
### 5.3 Certificate Pinning
|
||||
|
||||
**Archivo:** `di/NetworkModule.java:64-70`
|
||||
|
||||
La aplicación usa **Certificate Pinning** para prevenir ataques MITM:
|
||||
- Los certificados SSL esperados están en `PinningRepository`
|
||||
- Se cargan de forma asíncrona al inicio
|
||||
- Todas las peticiones verifican el certificado del servidor
|
||||
|
||||
---
|
||||
|
||||
## 6. Referencias de Código
|
||||
|
||||
### 6.1 Archivos Clave
|
||||
|
||||
| Archivo | Ubicación | Descripción |
|
||||
|---------|-----------|-------------|
|
||||
| `ServicePaths.java` | `serviceNetworking/` | Paths y headers estáticos |
|
||||
| `AuthHeaderInterceptor.java` | `serviceNetworking/interceptors/` | Generación de auth headers |
|
||||
| `NetworkModule.java` | `serviceNetworking/di/` | Configuración Retrofit/OkHttp |
|
||||
| `CirculationService.java` | `serviceNetworking/circulations/` | API de circulaciones |
|
||||
| `StationsService.java` | `serviceNetworking/stations/` | API de estaciones |
|
||||
| `StationObservationsService.java` | `serviceNetworking/stationObservations/` | API de observaciones |
|
||||
| `CompositionsService.java` | `serviceNetworking/compositions/` | API de composiciones |
|
||||
|
||||
### 6.2 Modelos de Request
|
||||
|
||||
| Modelo | Archivo | Uso |
|
||||
|--------|---------|-----|
|
||||
| `TrafficCirculationPathRequest` | `circulations/model/request/` | Departures, Arrivals, BetweenStations |
|
||||
| `OneOrSeveralPathsRequest` | `circulations/model/request/` | OnePaths, SeveralPaths, Compositions |
|
||||
| `OneStationRequest` | `stations/model/` | Detalles de estación |
|
||||
| `DetailedInfoDTO` | `stations/model/` | Configuración de info detallada |
|
||||
| `StationObservationsRequest` | `stationObservations/model/` | Observaciones de estaciones |
|
||||
|
||||
### 6.3 Líneas de Código Importantes
|
||||
|
||||
- Headers estáticos: `ServicePaths.java:67-76`
|
||||
- User-key circulaciones: `ServicePaths.java:67`
|
||||
- User-key estaciones: `ServicePaths.java:68`
|
||||
- AVISA login token: `ServicePaths.java:70`
|
||||
- Auth interceptor: `AuthHeaderInterceptor.java:38-83`
|
||||
- Base URL circulaciones: `NetworkModule.java:76`
|
||||
- Base URL estaciones: `NetworkModule.java:145`
|
||||
- Enum TrafficType: `TrafficType.java:16-21`
|
||||
- Enum State: `CirculationPathRequest.java:65-67`
|
||||
|
||||
---
|
||||
|
||||
## 7. Notas Adicionales
|
||||
|
||||
### 7.1 Serialización JSON
|
||||
|
||||
- **Biblioteca usada:** Moshi (configurado en `NetworkModule.java:87-96`)
|
||||
- **Formato:** Los nombres de campos en JSON coinciden exactamente con los nombres de propiedades en Java
|
||||
- **Null handling:** Los campos null se incluyen en el JSON
|
||||
- **Formato de fecha:** Timestamps en milisegundos (Long)
|
||||
|
||||
### 7.2 Consideraciones de Seguridad
|
||||
|
||||
1. **User-keys hardcodeadas:** Las claves API están en el código (fáciles de extraer)
|
||||
2. **Certificate Pinning:** Dificulta interceptar tráfico con proxy
|
||||
3. **Autenticación dinámica:** Los headers X-CanalMovil requieren conocer el algoritmo
|
||||
4. **AVISA token:** Credenciales Base64 en el código (pueden decodificarse)
|
||||
|
||||
### 7.3 Testing
|
||||
|
||||
Para probar estos endpoints:
|
||||
|
||||
1. **Extraer el algoritmo de autenticación:**
|
||||
- Analizar clase `ElcanoClientAuth` (no incluida en estos archivos)
|
||||
- O bien, usar Frida para hookear y capturar headers generados
|
||||
|
||||
2. **Bypass Certificate Pinning:**
|
||||
- Usar Frida con script de bypass SSL pinning
|
||||
- O modificar el APK para deshabilitar pinning
|
||||
|
||||
3. **Interceptar tráfico:**
|
||||
- mitmproxy con Frida
|
||||
- Burp Suite con Frida
|
||||
- Captura directa con tcpdump/Wireshark
|
||||
|
||||
---
|
||||
|
||||
## 8. Próximos Pasos
|
||||
|
||||
- [ ] Extraer y analizar clase `ElcanoClientAuth`
|
||||
- [ ] Reverse engineering del algoritmo de firma
|
||||
- [ ] Capturar tráfico real con Frida
|
||||
- [ ] Implementar generador de headers de autenticación
|
||||
- [ ] Probar endpoints con Postman/curl
|
||||
- [ ] Documentar respuestas de cada endpoint
|
||||
|
||||
---
|
||||
|
||||
**Última actualización:** 2025-12-04
|
||||
**Fuente:** APK decompilado de ADIF El Cano Móvil
|
||||
**Herramientas:** JADX, análisis manual de código Java
|
||||
Reference in New Issue
Block a user