SJ API med tidtabeller & tåglägen

Igår eftermiddag, dagen efter Trafikverket presenterade Läget i trafiken, lanserade SJ en app till Android. Jag började snabbt titta på datatrafiken till och från appen. Precis som i Trafikverkets fall handlar det om ett helt öppet och oskyddat API, dock inte lika exponerat.

SJ exponerade personlig data

Om du tittar lite närmare i svarsdatan från SJ:s API nedan, närmare bestämt i värdefältet för deviceID. Detta innehåller den privata, unika identifieraren för den enhet du använder när en ny bevakning skapas. SJ exponerar alltså denna data, om alla som valt att bevaka tåg via deras app.

Detta är väldigt kritiskt data, som varken Google eller Apple tillåter att utvecklare tillgängliggör. Med informationen, som Theodor nämner i sitt inlägg, är det exempelvis möjligt att skapa, ändra och ta bort andra användares bevakningar.

Jag uppmanar alla användare att ta bort alla sina bevakningar som skapats via appen, tills dess att SJ har säkrat sitt API.

SJ har bemött kritiken via Twitter

“Nu har vi svar. Det finns ingen personlig info i appen, inte ens UD ID. Det vi kallar device ID är endast kopplat till appen. Trots det ska vi göra om den så den blir krypterad så fort som möjligt.” — @SJ_AB

Intressant svar. Inget är fel, men det ska ändå åtgärdas? Detta är fel. Appen skickar UDID i klartext till SJ:s API. Theodor har tydliggjort detta med enkla skärmdumpar i sitt inlägg. Därefter var det dags att bekänna.

“Vi var övertygade om att vi hade rätt. Nu undersöker vi vad detta beror på och ska åtgärda snarast.” — @SJ_AB

Jättebra agerande av marknadsavdelningen tycker jag. Nu hoppas vi bara att SJ först kommunicerar problemet med sina användare och sedan stänger in användardatan snarast.

Uppdatering: SJ har nu säkrat den mest kritiska bristen i sitt API. Men fortfarande skickas iOS-enheters UDID i klartext till och från appen.

Metoder (deprecated)

Första versionen av SJ API fungerar inte längre som dokumenterat nedan. Se dokumentation av SJ API 2.

Get stations

GET /stations.json
Returnerar alla stationer. Exempel för /stations.json:

{
    "stations":
    [
        {
            "id": 1,
            "stationName": "Stockholm C",
            "city": "Stockholm C",
            "stationType": "sj"
        },
        …
        {
            "id": 924,
            "stationName": "Stenstorp",
            "city": "Stenstorp",
            "stationType": "sj"
        }
    ]
}

Get station

GET /station/#{id}.json
Returnerar given station. Exempel för /station/1.json:

{
    "id": 1,
    "stationName": "Stockholm C",
    "city": "Stockholm C",
    "stationType": "SJ"
}

Get station timetable

GET /stationTimeTable/#{id}.json
Returnerar tidtabell för given station. Exempel för /stationTimeTable/1.json:

{
    "id": 1,
    "stationName": "Stockholm C",
    "city": "Stockholm C",
    "stationType": "SJ",
    "arrivals":
        [
        {
            "trainNumber": "11",
            "time":
            {
                "scheduledTime": "2011-09-29T08:16",
                "newTime": "2011-09-29T08:18"
            },
            "track":
            {
                "scheduledTrack": "7",
                "newTrack": ""
            },
            "fromStationName": "Falun C",
            "toStationName": "Stockholm C",
            "stationNames":
            [
                "Falun C",
                "Borlänge C",
                "Säter",
                "Hedemora",
                "Avesta Krylbo",
                "Sala",
                "Uppsala C",
                "Arlanda C",
                "Stockholm C"
            ],
            "cancelled": false
        },
        …
        {
            "trainNumber": "861",
            "time":
            {
                "scheduledTime": "2011-09-29T19:49",
                "newTime":""
            },
            "track":
            {
                "scheduledTrack": "3",
                "newTrack": ""
            },
            "fromStationName": "Uppsala C",
            "toStationName": "Stockholm C",
            "stationNames":
            [
                "Uppsala C",
                "Knivsta",
                "Märsta",
                "Stockholm C"
            ],
            "cancelled": false
        },
        …
        {
            "trainNumber": "10759",
            "time":
            {
                "scheduledTime": "2011-09-29T20:00",
                "newTime": ""
            },
            "track":
            {
                "scheduledTrack": "7",
                "newTrack": ""
            },
            "fromStationName": "Örebro C",
            "toStationName": "Stockholm C",
            "stationNames":
            [
                "Örebro C",
                "Arboga",
                "Köping",
                "Västerås C",
                "Enköping",
                "Bålsta",
                "Sundbyberg",
                "Stockholm C"
            ],
            "cancelled": false
        }
    ],
    "departures":
    [
        {
            "trainNumber": "425",
            "time":
            {
                "scheduledTime": "2011-09-29T08:10",
                "newTime": ""
            },
            "track":
            {
                "scheduledTrack": "10",
                "newTrack": ""
            },
            "fromStationName": "Stockholm C",
            "toStationName": "Göteborg C",
            "stationNames":
            [
                "Stockholm C",
                "Södertälje Syd",
                "Katrineholm C",
                "Skövde C",
                "Alingsås",
                "Göteborg C"
            ],
            "cancelled":false
        },
        …
        {
            "trainNumber": "860",
            "time":
            {
                "scheduledTime": "2011-09-29T20:11",
                "newTime": ""
            },
            "track":
            {
                "scheduledTrack": "3",
                "newTrack": ""
            },
            "fromStationName": "Stockholm C",
            "toStationName": "Uppsala C",
            "stationNames":
            [
                "Stockholm C",
                "Märsta",
                "Knivsta",
                "Uppsala C"
            ],
            "cancelled":false
        }
    ]
}

Get train

GET /train/#{nr}.json
Returnerar position för givet tågnummer. Exempel för /train/220.json:

{
    "trainNumber": "220",
    "trainType": "",
    "fromStationName": "Linköping C",
    "toStationName": "Stockholm C",
    "trainPosition":
    {
        "latitude": 59.154098,
        "longitude": 17.762635,
        "timestamp": "2011-10-03T08:09"
    }
}

Get train timetable

GET /trainTimeTable/#{nr}.json
Returnerar stationer och position för givet tågnummer. Exempel för /trainTimeTable/538.json:

{
    "trainNumber": "538",
    "trainType": "",
    "fromStationName": "Malmö C",
    "toStationName": "Stockholm C",
    "trainPosition":
    {
        "latitude": 58.417037,
        "longitude": 15.624342,
        "timestamp": "2011-09-28T17:52"
    },
    "stops":
    [
        {
            "station":
            {
                "id": 3,
                "stationName": "Malmö C",
                "city": "Malmö C",
                "stationType": "SJ"
            },
            "arrivalTime":
            {
                "scheduledTime": "2011-09-28T13:09",
                "newTime":""
            },
            "departureTime":
            {
                "scheduledTime": "2011-09-28T13:17",
                "newTime": "2011-09-28T13:37"
            },
            "track":
            {
                "scheduledTrack": "6",
                "newTrack": ""
            },
            "isCancelled" :true,
            "isPassed":true
        },
        …
        {
            "station":
            {
                "id": 1,
                "stationName": "Stockholm C",
                "city": "Stockholm C",
                "stationType": "SJ"
            },
            "arrivalTime":
            {
                "scheduledTime": "2011-09-28T17:39",
                "newTime": "2011-09-28T19:30"
            },
            "departureTime":
            {
                "scheduledTime": "",
                "newTime": ""
            },
            "track":
            {
                "scheduledTrack": "10",
                "newTrack": ""
            },
            "isCancelled": false,
            "isPassed": false
        }
    ],
    "nextStop":7
}

Get subscriptions

GET /subscriptions.json
Returnerade tidigare alla bevakningar. Exempel för /subscriptions.json:

{
    "subscriptionsDTO":
    [
        {
            "id": "censurerad",
            "deviceID": "censurerad",
            "deviceType": "Android",
            "fromStationName": "Stockholm C",
            "toStationName": "Malmö C",
            "fromStationID": 1,
            "toStationID": 3,
            "enabled": true,
            "fromTime": "19:25",
            "toTime": "22:28",
            "weekdays":
            [
                "Tuesday",
                "Friday"
            ]
        },
        …
        {
            "id": "censurerad",
            "deviceID": "censurerad",
            "deviceType": "Android",
            "fromStationName": "Västerås C",
            "toStationName": "Katrineholm C",
            "fromStationID": 99,
            "toStationID": 166,
            "enabled": true,
            "fromTime": "06:55",
            "toTime": "08:00",
            "weekdays":
            [
                "Thursday",
                "Monday",
                "Tuesday",
                "Wednesday"
            ]
        }
    ]
}

New subscription

POST /subscriptions.json
Skapar ny bevakning. Exempelfrågan:

{
    "enabled":true,
    "deviceType": "iphone",
    "weekdays":
    [
        "Thursday"
    ],
    "deviceID": "censurerad",
    "toStationName": "Uppsala C",
    "fromStationName": "Stockholm C",
    "fromStationID": 1,
    "toStationID": 5,
    "fromTime": "10:41",
    "toTime": "11:41"
}

ger svaret:

{
    "id": "censurerad",
    "deviceID": "censurerad",
    "deviceType": "iphone",
    "fromStationName": "Stockholm C",
    "toStationName": "Uppsala C",
    "fromStationID": 1,
    "toStationID": 5,
    "enabled": true,
    "fromTime": "10:41",
    "toTime": "11:41",
    "weekdays":
    [
        "Thursday"
    ]
}

Andra som skrivit om SJ:s API

Tweet

Svara på inlägget


Hittills 5 kommentarer

Rickard Pettersson (@RickardP)

Bra jobbat att hitta detta, detta får mig att bli ännu mer sur på SJ, tänk om dem hade släppt detta för OSS utvecklare istället och hjälpt oss som redan har appar som gör samm sak som deras fast måste hämta data på dåligt sätt.

Rickard Pettersson (@RickardP)

Bara lekte lite och hittade: http://93.190.192.27/api/stations

Erik Pettersson

Rickard: Tack, jag har nu lagt till metoden för stationer.

Theodor Storm

Att alla device-ID:n exponeras i subscriptions är minst sagt besvärande. Med ett device-ID kan man unikt identifiera en telefon (och i förlängningen dess ägare), och här finns de dessutom kopplade till kundernas resevanor.

Erik Pettersson

Theodor: Mycket, jag kommer inte att använda appen just p.g.a. ”öppenheten”. Lite bekymmrande att Apple släppt igenom detta.