/* * Copyright (C) 2010 Nullbyte <http://nullbyte.eu> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.liato.bankdroid.banking.banks.lansforsakringar; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.liato.bankdroid.Helpers; import com.liato.bankdroid.banking.Account; import com.liato.bankdroid.banking.Bank; import com.liato.bankdroid.banking.Transaction; import com.liato.bankdroid.banking.banks.lansforsakringar.model.request.AccountsRequest; import com.liato.bankdroid.banking.banks.lansforsakringar.model.request.ChallengeRequest; import com.liato.bankdroid.banking.banks.lansforsakringar.model.request.LoginRequest; import com.liato.bankdroid.banking.banks.lansforsakringar.model.request.TransactionsRequest; import com.liato.bankdroid.banking.banks.lansforsakringar.model.response.AccountsResponse; import com.liato.bankdroid.banking.banks.lansforsakringar.model.response.ChallengeResponse; import com.liato.bankdroid.banking.banks.lansforsakringar.model.response.LoginResponse; import com.liato.bankdroid.banking.banks.lansforsakringar.model.response.NumberResponse; import com.liato.bankdroid.banking.banks.lansforsakringar.model.response.TransactionsResponse; import com.liato.bankdroid.banking.exceptions.BankChoiceException; import com.liato.bankdroid.banking.exceptions.BankException; import com.liato.bankdroid.banking.exceptions.LoginException; import com.liato.bankdroid.legacy.R; import com.liato.bankdroid.provider.IBankTypes; import com.liato.bankdroid.utils.StringUtils; import android.content.Context; import android.support.annotation.Nullable; import android.text.InputType; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.UUID; import eu.nullbyte.android.urllib.CertificateReader; import eu.nullbyte.android.urllib.Urllib; import timber.log.Timber; public class Lansforsakringar extends Bank { private static final String NAME = "Länsförsäkringar"; private static final int BANKTYPE_ID = IBankTypes.LANSFORSAKRINGAR; private static final int INPUT_TYPE_USERNAME = InputType.TYPE_CLASS_PHONE; private static final int INPUT_TYPE_PASSWORD = InputType.TYPE_CLASS_PHONE; private static final String INPUT_HINT_USERNAME = "ÅÅÅÅMMDDXXXX"; private static final String API_BASEURL = "https://mobil.lansforsakringar.se/appoutlet/"; private ObjectMapper mObjectMapper = new ObjectMapper(); private HashMap<String, String> mAccountLedger = new HashMap<>(); public Lansforsakringar(Context context) { super(context, R.drawable.logo_lansforsakringar); super.inputTypeUsername = INPUT_TYPE_USERNAME; super.inputTypePassword = INPUT_TYPE_PASSWORD; super.inputHintUsername = INPUT_HINT_USERNAME; mObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mObjectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false); mObjectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); } @Override public int getBanktypeId() { return BANKTYPE_ID; } @Override public String getName() { return NAME; } public Urllib login() throws LoginException, BankException, IOException { urlopen = new Urllib(context, CertificateReader.getCertificates(context, R.raw.cert_lansforsakringar)); urlopen.addHeader("Content-Type", "application/json; charset=UTF-8"); urlopen.addHeader("DeviceId", UUID.randomUUID().toString()); urlopen.addHeader("deviceInfo", "Galaxy Nexus;4.1.1;1.8;Portrait"); urlopen.addHeader("Accept", "application/json; charset=UTF-8"); //TODO: Change user-agent to "lf-android-app" if they block Bankdroid //urlopen.setUserAgent("lf-android-app"); NumberResponse nr = readJsonValue(API_BASEURL + "security/client", null, NumberResponse.class); ChallengeResponse cr = readJsonValue(API_BASEURL + "security/client", objectAsJson( new ChallengeRequest(nr.getNumber(), nr.getNumberPair(), generateChallenge(nr.getNumber()))), ChallengeResponse.class); urlopen.addHeader("Ctoken", cr.getToken()); try { LoginResponse lr = readJsonValue(API_BASEURL + "security/user", objectAsJson(new LoginRequest(getUsername(), getPassword())), LoginResponse.class); urlopen.addHeader("Utoken", lr.getTicket()); } catch (Exception e) { throw new LoginException(res.getText(R.string.invalid_username_password).toString(), e); } return urlopen; } private <T> T readJsonValue(InputStream is, Class<T> valueType) throws BankException, IOException { try { return mObjectMapper.readValue(is, valueType); } catch (JsonParseException | JsonMappingException e) { throw new BankException(e.getMessage(), e); } finally { try { is.close(); } catch (IOException e) { Timber.w(e, "Closing JSON stream failed"); } } } private <T> T readJsonValue(String url, String postData, Class<T> valueType) throws BankException, IOException { return readJsonValue(urlopen.openStream(url, postData, false), valueType); } @Nullable private String objectAsJson(Object value) { try { return mObjectMapper.writeValueAsString(value); } catch (JsonProcessingException e) { Timber.w(e, "Failed converting Object to JSON"); } return null; } private String generateChallenge(int originalChallenge) { try { String h = Integer.toHexString( originalChallenge + (1000 * 20 / 4) + 100 * (18 / 3) + 10 * (2 / 2) + 6); MessageDigest md = MessageDigest.getInstance("SHA-1"); byte[] messageDigest = md.digest(StringUtils.getBytes(h)); BigInteger number = new BigInteger(1, messageDigest); String md5 = number.toString(16); while (md5.length() < 40) { md5 = "0" + md5; } return md5; } catch (NoSuchAlgorithmException e) { Timber.w(e, "Länsförsäkringar: Error generating challenge"); } return ""; } @Override public void update() throws BankException, LoginException, BankChoiceException, IOException { super.update(); if (getUsername().isEmpty() || getPassword().isEmpty()) { throw new LoginException(res.getText(R.string.invalid_username_password).toString()); } urlopen = login(); mAccountLedger.clear(); AccountsResponse ar = readJsonValue(API_BASEURL + "account/bytype", objectAsJson(new AccountsRequest(AccountsRequest.Type.CHECKING)), AccountsResponse.class); for (com.liato.bankdroid.banking.banks.lansforsakringar.model.response.Account a : ar .getAccounts()) { accounts.add(new Account(a.getAccountName(), new BigDecimal(a.getBalance()), a.getAccountNumber())); //a.getLedger() should be saved to database, used when fetching transactions mAccountLedger.put(a.getAccountNumber(), a.getLedger()); balance = balance.add(new BigDecimal(a.getBalance())); } ar = readJsonValue(API_BASEURL + "account/bytype", objectAsJson(new AccountsRequest(AccountsRequest.Type.SAVING)), AccountsResponse.class); for (com.liato.bankdroid.banking.banks.lansforsakringar.model.response.Account a : ar .getAccounts()) { accounts.add(new Account(a.getAccountName(), new BigDecimal(a.getBalance()), a.getAccountNumber())); mAccountLedger.put(a.getAccountNumber(), a.getLedger()); balance = balance.add(new BigDecimal(a.getBalance())); } if (accounts.isEmpty()) { throw new BankException(res.getText(R.string.no_accounts_found).toString()); } super.updateComplete(); } @Override public void updateTransactions(Account account, Urllib urlopen) throws LoginException, BankException, IOException { super.updateTransactions(account, urlopen); // No transaction history for funds and loans if (account.getType() != Account.REGULAR) { return; } ArrayList<Transaction> transactions = new ArrayList<>(); //TODO: Get upcoming transactions? //TransactionsResponse tr = readJsonValue(API_BASEURL + "account/upcoming", objectAsJson(new UpcomingTransactionsRequest(account.getId())), TransactionsResponse.class); try { TransactionsResponse tr = readJsonValue(API_BASEURL + "account/transaction", objectAsJson(new TransactionsRequest(0, mAccountLedger.containsKey(account.getId()) ? mAccountLedger .get(account.getId()) : "DEPIOSIT", account.getId())), TransactionsResponse.class); for (com.liato.bankdroid.banking.banks.lansforsakringar.model.response.Transaction t : tr .getTransactions()) { //TODO: Set locale to Europe/Stockholm on date? transactions .add(new Transaction(Helpers.formatDate(new Date(t.getTransactiondate())), t.getText(), new BigDecimal(t.getAmmount()))); } account.setTransactions(transactions); } catch (BankException e) { Timber.e(e, "Failed updating Länsförsäkringar transactions"); } super.updateComplete(); } }