1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.federatedcompute.services.data; 18 19 import static com.android.federatedcompute.services.data.ODPAuthorizationTokenContract.ODP_AUTHORIZATION_TOKEN_TABLE; 20 21 import android.annotation.NonNull; 22 import android.content.ContentValues; 23 import android.content.Context; 24 import android.database.Cursor; 25 import android.database.sqlite.SQLiteDatabase; 26 import android.database.sqlite.SQLiteException; 27 28 import com.android.federatedcompute.internal.util.LogUtil; 29 import com.android.federatedcompute.services.data.ODPAuthorizationTokenContract.ODPAuthorizationTokenColumns; 30 import com.android.odp.module.common.Clock; 31 import com.android.odp.module.common.MonotonicClock; 32 33 import com.google.common.annotations.VisibleForTesting; 34 35 public class ODPAuthorizationTokenDao { 36 private static final String TAG = ODPAuthorizationTokenDao.class.getSimpleName(); 37 38 private final FederatedComputeDbHelper mDbHelper; 39 40 private final Clock mClock; 41 42 private static volatile ODPAuthorizationTokenDao sSingletonInstance; 43 ODPAuthorizationTokenDao(FederatedComputeDbHelper dbHelper, Clock clock)44 private ODPAuthorizationTokenDao(FederatedComputeDbHelper dbHelper, Clock clock) { 45 mDbHelper = dbHelper; 46 mClock = clock; 47 } 48 49 /** 50 * @return an instance of ODPAuthorizationTokenDao given a context 51 */ 52 @NonNull getInstance(Context context)53 public static ODPAuthorizationTokenDao getInstance(Context context) { 54 if (sSingletonInstance == null) { 55 synchronized (ODPAuthorizationTokenDao.class) { 56 if (sSingletonInstance == null) { 57 sSingletonInstance = new ODPAuthorizationTokenDao( 58 FederatedComputeDbHelper.getInstance(context), 59 MonotonicClock.getInstance() 60 ); 61 } 62 } 63 } 64 return sSingletonInstance; 65 } 66 67 /** Return a test instance with in-memory database. It is for test only. */ 68 @VisibleForTesting getInstanceForTest(Context context)69 public static ODPAuthorizationTokenDao getInstanceForTest(Context context) { 70 if (sSingletonInstance == null) { 71 synchronized (ODPAuthorizationTokenDao.class) { 72 if (sSingletonInstance == null) { 73 sSingletonInstance = new ODPAuthorizationTokenDao( 74 FederatedComputeDbHelper.getInstanceForTest(context), 75 MonotonicClock.getInstance() 76 ); 77 } 78 } 79 } 80 return sSingletonInstance; 81 } 82 83 /** Insert a token to the odp authorization token table. */ insertAuthorizationToken(ODPAuthorizationToken authorizationToken)84 public boolean insertAuthorizationToken(ODPAuthorizationToken authorizationToken) { 85 SQLiteDatabase db = mDbHelper.safeGetWritableDatabase(); 86 if (db == null) { 87 throw new SQLiteException(TAG + ": Failed to open database."); 88 } 89 90 ContentValues values = new ContentValues(); 91 values.put(ODPAuthorizationTokenColumns.OWNER_IDENTIFIER, 92 authorizationToken.getOwnerIdentifier()); 93 values.put(ODPAuthorizationTokenColumns.AUTHORIZATION_TOKEN, 94 authorizationToken.getAuthorizationToken()); 95 values.put(ODPAuthorizationTokenColumns.CREATION_TIME, 96 authorizationToken.getCreationTime()); 97 values.put(ODPAuthorizationTokenColumns.EXPIRY_TIME, 98 authorizationToken.getExpiryTime()); 99 100 101 long jobId = 102 db.insertWithOnConflict( 103 ODP_AUTHORIZATION_TOKEN_TABLE, "", values, SQLiteDatabase.CONFLICT_REPLACE); 104 return jobId != -1; 105 } 106 107 /** Get an ODP adopter's unexpired authorization token. 108 * @return an unexpired authorization token. */ getUnexpiredAuthorizationToken(String ownerIdentifier)109 public ODPAuthorizationToken getUnexpiredAuthorizationToken(String ownerIdentifier) { 110 String selection = ODPAuthorizationTokenColumns.EXPIRY_TIME + " > ? " + "AND " 111 + ODPAuthorizationTokenColumns.OWNER_IDENTIFIER + " = ?"; 112 String[] selectionArgs = {String.valueOf(mClock.currentTimeMillis()), ownerIdentifier}; 113 String orderBy = ODPAuthorizationTokenColumns.EXPIRY_TIME + " DESC"; 114 return readTokenFromDatabase(selection, selectionArgs, orderBy); 115 } 116 117 /** Delete an ODP adopter's authorization token. 118 * @return the number of rows deleted. */ deleteAuthorizationToken(String ownerIdentifier)119 public int deleteAuthorizationToken(String ownerIdentifier) { 120 SQLiteDatabase db = mDbHelper.safeGetWritableDatabase(); 121 if (db == null) { 122 throw new SQLiteException(TAG + ": Failed to open database."); 123 } 124 String whereClause = ODPAuthorizationTokenColumns.OWNER_IDENTIFIER + " = ?"; 125 String[] whereArgs = {ownerIdentifier}; 126 int deletedRows = db.delete(ODP_AUTHORIZATION_TOKEN_TABLE, whereClause, whereArgs); 127 LogUtil.d(TAG, "Deleted %d expired tokens for %s from database", deletedRows, 128 ownerIdentifier); 129 return deletedRows; 130 } 131 132 133 /** Batch delete all expired authorization tokens. 134 * @return the number of rows deleted. */ deleteExpiredAuthorizationTokens()135 public int deleteExpiredAuthorizationTokens() { 136 SQLiteDatabase db = mDbHelper.safeGetWritableDatabase(); 137 if (db == null) { 138 throw new SQLiteException(TAG + ": Failed to open database."); 139 } 140 String whereClause = ODPAuthorizationTokenColumns.EXPIRY_TIME + " < ?"; 141 String[] whereArgs = { String.valueOf(mClock.currentTimeMillis()) }; 142 int deletedRows = db.delete(ODP_AUTHORIZATION_TOKEN_TABLE, whereClause, whereArgs); 143 LogUtil.d(TAG, "Deleted %d expired tokens", deletedRows); 144 return deletedRows; 145 } 146 147 148 readTokenFromDatabase( String selection, String[] selectionArgs, String orderBy)149 private ODPAuthorizationToken readTokenFromDatabase( 150 String selection, String[] selectionArgs, String orderBy) { 151 SQLiteDatabase db = mDbHelper.safeGetReadableDatabase(); 152 if (db == null) { 153 throw new SQLiteException(TAG + ": Failed to open database."); 154 } 155 156 String[] selectColumns = { 157 ODPAuthorizationTokenColumns.OWNER_IDENTIFIER, 158 ODPAuthorizationTokenColumns.AUTHORIZATION_TOKEN, 159 ODPAuthorizationTokenColumns.CREATION_TIME, 160 ODPAuthorizationTokenColumns.EXPIRY_TIME, 161 }; 162 163 Cursor cursor = null; 164 ODPAuthorizationToken authToken = null; 165 try { 166 cursor = 167 db.query( 168 ODP_AUTHORIZATION_TOKEN_TABLE, 169 selectColumns, 170 selection, 171 selectionArgs, 172 /* groupBy= */ null, 173 /* having= */ null, 174 /* orderBy= */ orderBy, 175 /* limit= */ String.valueOf(1)); 176 while (cursor.moveToNext()) { 177 ODPAuthorizationToken.Builder encryptionKeyBuilder = 178 new ODPAuthorizationToken.Builder( 179 cursor.getString( 180 cursor.getColumnIndexOrThrow( 181 ODPAuthorizationTokenColumns.OWNER_IDENTIFIER)), 182 cursor.getString( 183 cursor.getColumnIndexOrThrow( 184 ODPAuthorizationTokenColumns.AUTHORIZATION_TOKEN)), 185 cursor.getLong( 186 cursor.getColumnIndexOrThrow( 187 ODPAuthorizationTokenColumns.CREATION_TIME)), 188 cursor.getLong( 189 cursor.getColumnIndexOrThrow( 190 ODPAuthorizationTokenColumns.EXPIRY_TIME)) 191 ); 192 authToken = encryptionKeyBuilder.build(); 193 } 194 } finally { 195 if (cursor != null) { 196 cursor.close(); 197 } 198 } 199 return authToken; 200 } 201 } 202