/** * Copyright 2005-2014 Restlet * * The contents of this file are subject to the terms of one of the following * open source licenses: Apache 2.0 or LGPL 3.0 or LGPL 2.1 or CDDL 1.0 or EPL * 1.0 (the "Licenses"). You can select the license that you prefer but you may * not use this file except in compliance with one of these Licenses. * * You can obtain a copy of the Apache 2.0 license at * http://www.opensource.org/licenses/apache-2.0 * * You can obtain a copy of the LGPL 3.0 license at * http://www.opensource.org/licenses/lgpl-3.0 * * You can obtain a copy of the LGPL 2.1 license at * http://www.opensource.org/licenses/lgpl-2.1 * * You can obtain a copy of the CDDL 1.0 license at * http://www.opensource.org/licenses/cddl1 * * You can obtain a copy of the EPL 1.0 license at * http://www.opensource.org/licenses/eclipse-1.0 * * See the Licenses for the specific language governing permissions and * limitations under the Licenses. * * Alternatively, you can obtain a royalty free commercial license with less * limitations, transferable or non-transferable, directly at * http://www.restlet.com/products/restlet-framework * * Restlet is a registered trademark of Restlet */ package org.deviceconnect.android.localoauth.oauthserver.db; import java.util.Map; import java.util.Set; import java.util.UUID; import org.restlet.ext.oauth.PackageInfoOAuth; import org.restlet.ext.oauth.internal.AbstractClientManager; import org.restlet.ext.oauth.internal.Client; import org.restlet.ext.oauth.internal.Client.ClientType; import android.database.Cursor; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.os.Bundle; /** * SQLite版ClientManager(RestletのMemoryClientManagerをベースに実装). */ public class SQLiteClientManager extends AbstractClientManager { /** * DBオブジェクト. */ private SQLiteDatabase mDb = null; /** * DBオブジェクトを設定する. * * @param db DBオブジェクト */ public void setDb(final SQLiteDatabase db) { mDb = db; } /** * クライアント作成. * @param packageInfo パッケージ情報 * @param clientId クライアントID * @param clientSecret クライアントシークレット * @param clientType クライアントタイプ * @param redirectURIs リダイレクトURIs * @param properties プロパティ * @return クライアントデータ */ protected Client createClient(final PackageInfoOAuth packageInfo, final String clientId, final char[] clientSecret, final ClientType clientType, final String[] redirectURIs, final Map<String, Object> properties) { /* clientデータ設定 */ SQLiteClient client = new SQLiteClient(UUID.randomUUID().toString(), packageInfo, clientType, redirectURIs, properties); if (clientSecret != null) { client.setClientSecret(clientSecret); } /* DBに1件追加(失敗したらSQLiteException発生) */ client.dbInsert(mDb); return client; } /** * クライアントIDをキーにクライアントデータ削除. * @param id クライアントID */ public void deleteClient(final String id) { if (mDb != null) { Bundle where = new Bundle(); where.putString(SQLiteClient.DATA_TYPE_STRING + "," + SQLiteClient.CLIENTID_FIELD, id); dbDeleteClients(mDb, where); } else { throw new SQLiteException("DBがオープンされていません。"); } } /** * 長時間利用されていないクライアントをクリーンアップする.<br> * (1)clientsテーブルにあるがtokensテーブルにトークンが無い場合、clientsの登録日時がしきい値を越えていたら削除する。<br> * (2)clientsテーブルにあるがtokensテーブルにトークンが有る場合、tokensの登録日時がしきい値を越えていたら削除する。<br> * ※(2)の場合、トークンの有効期限内なら削除しない。 * @param clientCleanupTime クライアントをクリーンアップする未アクセス時間[sec]. */ public void cleanupClient(final int clientCleanupTime) { if (mDb != null) { final long msec = 1000; /* 1[sec] = 1000[msec] */ final long cleanupTime = System.currentTimeMillis() - clientCleanupTime * msec; /* (1)に該当するclientsレコードを削除する */ String sql1 = "delete from clients where " + "not exists ( select * from tokens where clients.client_id = tokens.client_id ) " + "and clients.registration_date < " + cleanupTime + ";"; mDb.execSQL(sql1); /* (2)に該当するclientsレコードを削除する */ String sql2 = "delete from clients where " + "exists (select * from tokens where clients.client_id = tokens.client_id) " + "and not exists ( select * from tokens, scopes " + "where clients.client_id = tokens.client_id and tokens.id = scopes.tokens_tokenid " + "and (scopes.timestamp + scopes.expire_period) > " + cleanupTime + ")"; mDb.execSQL(sql2); /* (2)で削除されたclientsのtokensレコードを削除する(clientsがリンク切れしたtokensとscopesを削除する) */ String sql3 = "delete from scopes where not exists (" + "select * from tokens, clients " + "where scopes.tokens_tokenid = tokens.id and tokens.client_id = clients.client_id);"; mDb.execSQL(sql3); String sql4 = "delete from tokens where not exists (" + "select * from clients where tokens.client_id = clients.client_id);"; mDb.execSQL(sql4); } else { throw new SQLiteException("DBがオープンされていません。"); } } /** * クライアントIDをキーにDB検索し該当するクライアントを返す. * @param id クライアントID * @return not null: クライアントIDが一致するクライアント / null: クライアントIDが一致するクライアント無し */ public Client findById(final String id) { if (mDb != null) { Bundle where = new Bundle(); where.putString(SQLiteClient.DATA_TYPE_STRING + "," + SQLiteClient.CLIENTID_FIELD, id); Client[] clients = dbLoadClients(mDb, where); if (clients == null || clients.length == 0) { return null; } else if (clients.length == 1) { return clients[0]; } else { throw new SQLiteException("クライアントIDが2件以上のクライアントデータに設定されています。"); } } else { throw new SQLiteException("DBがオープンされていません。"); } } /** * サービスIDをキーにDB検索し該当するクライアントを返す. * @param serviceId サービスID * @return not null: サービスIDが一致するクライアント / null: サービスIDが一致するクライアント無し */ public Client findByServiceId(final String serviceId) { if (mDb != null) { Bundle where = new Bundle(); where.putString(SQLiteClient.DATA_TYPE_STRING + "," + SQLiteClient.DEVICEID_FIELD, serviceId); Client[] clients = dbLoadClients(mDb, where); if (clients == null || clients.length == 0) { return null; } else if (clients.length == 1) { return clients[0]; } else { throw new SQLiteException("クライアントIDが2件以上のクライアントデータに設定されています。"); } } else { throw new SQLiteException("DBがオープンされていません。"); } } /** * 有効なclientsレコード数をカウントして返す. * @return 有効なclientsレコード数 */ public int countClients() { if (mDb != null) { int count = dbCountClients(); return count; } else { throw new SQLiteException("DBがオープンされていません。"); } } /** * パッケージ情報をキーにDB検索し該当するクライアントを返す。(追加). * @param packageInfo パッケージ情報 * @return not null: パッケージ情報が一致するクライアント / null: パッケージ情報が一致するクライアント無し */ @Override public Client findByPackageInfo(final PackageInfoOAuth packageInfo) { if (mDb != null) { Bundle where = new Bundle(); where.putString(SQLiteClient.DATA_TYPE_STRING + "," + SQLiteClient.PACKAGENAME_FIELD, packageInfo.getPackageName()); where.putString(SQLiteClient.DATA_TYPE_STRING + "," + SQLiteClient.DEVICEID_FIELD, packageInfo.getServiceId()); SQLiteClient[] clients = dbLoadClients(mDb, where); if (clients == null || clients.length == 0) { return null; } else if (clients.length == 1) { return clients[0]; } else { throw new SQLiteException("クライアントIDが2件以上のクライアントデータに設定されています。"); } } else { throw new SQLiteException("DBがオープンされていません。"); } } /** * 指定条件でclientデータをDBから読み込む. * * @param db DBオブジェクト * @param where where条件(key = value)。複数あるときはAnd条件になる。 * @return 条件に一致したclientデータ配列。該当データが0件ならnull。 */ private SQLiteClient[] dbLoadClients(final SQLiteDatabase db, final Bundle where) { SQLiteClient[] result = null; String tables = LocalOAuthOpenHelper.CLIENTS_TABLE; String[] columns = SQLiteClient.CLIENT_ALL_FIELIDS; String selection = getSelection(where); Cursor c = null; try { c = db.query(tables, columns, selection, null, null, null, null); if (c.moveToFirst()) { int count = c.getCount(); if (count > 0) { result = new SQLiteClient[count]; for (int i = 0; i < count; i++) { SQLiteClient client = new SQLiteClient(); final int idColumnIndex = c.getColumnIndex(SQLiteClient.ID_FIELD); if (!c.isNull(idColumnIndex)) { client.setId(c.getLong(idColumnIndex)); } final int clientIdColumnIndex = c.getColumnIndex(SQLiteClient.CLIENTID_FIELD); if (!c.isNull(clientIdColumnIndex)) { client.setClientId(c.getString(clientIdColumnIndex)); } String packageName = null; final int packageNameColumnIndex = c.getColumnIndex(SQLiteClient.PACKAGENAME_FIELD); if (!c.isNull(packageNameColumnIndex)) { packageName = c.getString(packageNameColumnIndex); } String serviceId = null; final int serviceIdColumnIndex = c.getColumnIndex(SQLiteClient.DEVICEID_FIELD); if (!c.isNull(serviceIdColumnIndex)) { serviceId = c.getString(serviceIdColumnIndex); } PackageInfoOAuth packageInfo = new PackageInfoOAuth(packageName, serviceId); client.setPackageInfo(packageInfo); final int clientSecretColumnIndex = c.getColumnIndex(SQLiteClient.CLIENTSECRET_FIELD); if (!c.isNull(clientSecretColumnIndex)) { client.setClientSecret(c.getString(clientSecretColumnIndex).toCharArray()); } final int clientTypeColumnIndex = c.getColumnIndex(SQLiteClient.CLIENTTYPE_FIELD); if (!c.isNull(clientTypeColumnIndex)) { client.setClientType(ClientType.values()[c.getInt(clientTypeColumnIndex)]); } final int registrationDateColumnIndex = c.getColumnIndex(SQLiteClient.REGISTRATION_DATE_FIELD); if (!c.isNull(registrationDateColumnIndex)) { client.setRegistrationDate(c.getLong(registrationDateColumnIndex)); } result[i] = client; c.moveToNext(); } } } } finally { if (c != null) { c.close(); } } return result; } /** * 指定条件でクライアントデータをDBから読み込む. * * @param db DBオブジェクト * @param where where条件(key = value)。複数あるときはAnd条件になる。 */ private void dbDeleteClients(final SQLiteDatabase db, final Bundle where) { String table = LocalOAuthOpenHelper.CLIENTS_TABLE; String selection = getSelection(where); db.delete(table, selection, null); } /** * 有効なclientsレコード数をカウントして返す. * @return 有効なclientsレコード数 */ private int dbCountClients() { if (mDb != null) { String sql = "select count(*) from " + LocalOAuthOpenHelper.CLIENTS_TABLE; Cursor c = null; try { c = mDb.rawQuery(sql, null); if (c.moveToLast()) { return c.getInt(0); } return 0; } finally { if (c != null) { c.close(); } } } else { throw new SQLiteException("DBがオープンされていません。"); } } /** * Bundle型の変数に設定したwhere条件をSQLのselection部に設定する文字列に変換して返す. * * @param where where条件 * @return SQLite関数のwhere部に指定する文字列 */ private String getSelection(final Bundle where) { String selection = null; if (where != null) { Set<String> whereKeys = where.keySet(); if (whereKeys.size() > 0) { selection = ""; int i = 0; for (String strWhereKey : whereKeys) { String[] splitData = strWhereKey.split(","); if (splitData == null || splitData.length != 2) { throw new IllegalArgumentException("whereは { <データタイプ>,<whereキー> } の書式で設定して下さい。"); } String whereDataType = splitData[0]; String whereKeyData = splitData[1]; if (i > 0) { selection += " and "; } if (whereDataType.equals(SQLiteClient.DATA_TYPE_LONG)) { long whereValue = where.getLong(strWhereKey); selection += whereKeyData + " = " + whereValue; } else if (whereDataType.equals(SQLiteClient.DATA_TYPE_STRING)) { String whereValue = where.getString(strWhereKey); if (whereValue == null) { selection += whereKeyData + " is null"; } else { selection += whereKeyData + " = " + DatabaseUtils.sqlEscapeString(whereValue); } } else { throw new IllegalArgumentException("whereのデータタイプが認識できません。"); } i++; } } } return selection; } }