-
Notifications
You must be signed in to change notification settings - Fork 110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MWA 2.0 Auth Repository #605
Changes from 2 commits
6ffd522
daeb2df
b6b9792
297ab98
781aec4
f44a4df
872af79
027bebe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package com.solana.mobilewalletadapter.walletlib.authorization; | ||
|
||
import android.net.Uri; | ||
|
||
import androidx.annotation.IntRange; | ||
import androidx.annotation.NonNull; | ||
import androidx.annotation.Nullable; | ||
|
||
/* package */ class AccountRecord { | ||
@IntRange(from = 1) | ||
final int id; | ||
|
||
@NonNull | ||
final byte[] publicKeyRaw; | ||
|
||
@Nullable | ||
final String accountLabel; | ||
|
||
@Nullable | ||
final Uri icon; | ||
|
||
@Nullable | ||
final String[] chains; | ||
|
||
@Nullable | ||
final String[] features; | ||
|
||
AccountRecord(@IntRange(from = 1) int id, | ||
@NonNull byte[] publicKeyRaw, | ||
@Nullable String accountLabel, | ||
@Nullable Uri icon, | ||
@Nullable String[] chains, | ||
@Nullable String[] features) { | ||
this.id = id; | ||
this.publicKeyRaw = publicKeyRaw; | ||
this.accountLabel = accountLabel; | ||
this.icon = icon; | ||
this.chains = chains; | ||
this.features = features; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package com.solana.mobilewalletadapter.walletlib.authorization; | ||
|
||
import android.content.ContentValues; | ||
import android.database.Cursor; | ||
import android.database.sqlite.SQLiteCursor; | ||
import android.database.sqlite.SQLiteDatabase; | ||
import android.database.sqlite.SQLiteStatement; | ||
import android.net.Uri; | ||
import android.text.TextUtils; | ||
|
||
import androidx.annotation.IntRange; | ||
import androidx.annotation.NonNull; | ||
import androidx.annotation.Nullable; | ||
|
||
public class AccountRecordsDao extends DbContentProvider<AccountRecord> | ||
implements AccountRecordsDaoInterface, AccountRecordsSchema { | ||
|
||
public AccountRecordsDao(SQLiteDatabase db) { super(db); } | ||
|
||
@NonNull | ||
@Override | ||
protected AccountRecord cursorToEntity(@NonNull Cursor cursor) { | ||
final int publicKeyId = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_ACCOUNTS_ID)); | ||
final byte[] publicKey = cursor.getBlob(cursor.getColumnIndexOrThrow(COLUMN_ACCOUNTS_PUBLIC_KEY_RAW)); | ||
final String accountLabel = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_ACCOUNTS_LABEL)); | ||
final String accountIconStr = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_ACCOUNTS_ICON)); | ||
final String chainsString = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_ACCOUNTS_CHAINS)); | ||
final String featuresString = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_ACCOUNTS_FEATURES)); | ||
final Uri accountIcon = Uri.parse(accountIconStr); | ||
final String[] chains = deserialize(chainsString); | ||
final String[] features = deserialize(featuresString); | ||
return new AccountRecord(publicKeyId, publicKey, accountLabel, accountIcon, chains, features); | ||
} | ||
|
||
@Override | ||
public long insert(@NonNull byte[] publicKey, | ||
@Nullable String accountLabel, | ||
@Nullable Uri accountIcon, | ||
@Nullable String[] chains, | ||
@Nullable String[] features) { | ||
final ContentValues accountContentValues = new ContentValues(4); | ||
accountContentValues.put(COLUMN_ACCOUNTS_PUBLIC_KEY_RAW, publicKey); | ||
accountContentValues.put(COLUMN_ACCOUNTS_LABEL, accountLabel); | ||
accountContentValues.put(COLUMN_ACCOUNTS_ICON, accountIcon != null ? accountIcon.toString() : null); | ||
accountContentValues.put(COLUMN_ACCOUNTS_CHAINS, chains != null ? serialize(chains) : null); | ||
accountContentValues.put(COLUMN_ACCOUNTS_FEATURES, features != null ? serialize(features) : null); | ||
return super.insert(TABLE_ACCOUNTS, accountContentValues); | ||
} | ||
|
||
@Nullable | ||
@Override | ||
public AccountRecord query(@NonNull byte[] publicKey) { | ||
final SQLiteDatabase.CursorFactory accountCursorFactory = (db1, masterQuery, editTable, query) -> { | ||
query.bindBlob(1, publicKey); | ||
return new SQLiteCursor(masterQuery, editTable, query); | ||
}; | ||
try (final Cursor cursor = super.queryWithFactory(accountCursorFactory, | ||
TABLE_ACCOUNTS, | ||
ACCOUNTS_COLUMNS, | ||
COLUMN_ACCOUNTS_PUBLIC_KEY_RAW + "=?", | ||
null)) { | ||
if (!cursor.moveToNext()) { | ||
return null; | ||
} | ||
return cursorToEntity(cursor); | ||
} | ||
} | ||
|
||
@Override | ||
public void deleteUnreferencedAccounts() { | ||
final SQLiteStatement deleteUnreferencedPublicKeys = super.compileStatement( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
"DELETE FROM " + TABLE_ACCOUNTS + | ||
" WHERE " + COLUMN_ACCOUNTS_ID + " NOT IN " + | ||
"(SELECT DISTINCT " + AuthorizationsSchema.COLUMN_AUTHORIZATIONS_ACCOUNT_ID + | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed with migration from |
||
" FROM " + AuthorizationsSchema.TABLE_AUTHORIZATIONS + ')'); | ||
deleteUnreferencedPublicKeys.executeUpdateDelete(); | ||
} | ||
|
||
// using a long alphanumeric divider reduces the chance of an array element matching the divider | ||
private static final String ARRAY_DIVIDER = "#a1r2ra5yd2iv1i9der"; | ||
|
||
private String serialize(String[] content){ return TextUtils.join(ARRAY_DIVIDER, content); } | ||
|
||
private static String[] deserialize(String content){ | ||
return content.split(ARRAY_DIVIDER); | ||
} | ||
|
||
/*package*/ static AccountRecord buildAccountRecordFromRaw(@IntRange(from = 1) int id, | ||
@NonNull byte[] publicKeyRaw, | ||
@Nullable String accountLabel, | ||
@Nullable String iconStr, | ||
@Nullable String chainsStr, | ||
@Nullable String featuresStr) { | ||
final Uri icon = iconStr != null ? Uri.parse(iconStr) : null; | ||
final String[] chains = chainsStr != null ? deserialize(chainsStr) : null; | ||
final String[] features = featuresStr != null ? deserialize(featuresStr) : null; | ||
return new AccountRecord(id, publicKeyRaw, accountLabel, icon, chains, features); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.solana.mobilewalletadapter.walletlib.authorization; | ||
|
||
import android.net.Uri; | ||
|
||
import androidx.annotation.IntRange; | ||
import androidx.annotation.NonNull; | ||
import androidx.annotation.Nullable; | ||
|
||
/*package*/ interface AccountRecordsDaoInterface { | ||
|
||
@IntRange(from = -1) | ||
long insert(@NonNull byte[] publicKey, @Nullable String accountLabel, @Nullable Uri accountIcon, | ||
@Nullable String[] chains, @Nullable String[] features); | ||
|
||
@Nullable | ||
AccountRecord query(@NonNull byte[] publicKey); | ||
|
||
void deleteUnreferencedAccounts(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package com.solana.mobilewalletadapter.walletlib.authorization; | ||
|
||
/*package*/ interface AccountRecordsSchema { | ||
String TABLE_ACCOUNTS = "accounts"; | ||
String COLUMN_ACCOUNTS_ID = "id"; // type: long | ||
String COLUMN_ACCOUNTS_PUBLIC_KEY_RAW = "public_key_raw"; // type: byte[] | ||
String COLUMN_ACCOUNTS_LABEL = "label"; // type: String | ||
String COLUMN_ACCOUNTS_ICON = "icon"; // type: String | ||
String COLUMN_ACCOUNTS_CHAINS = "chains"; // type: String | ||
Michaelsulistio marked this conversation as resolved.
Show resolved
Hide resolved
|
||
String COLUMN_ACCOUNTS_FEATURES = "features"; // type: String | ||
|
||
String CREATE_TABLE_ACCOUNTS = | ||
"CREATE TABLE " + TABLE_ACCOUNTS + " (" + | ||
COLUMN_ACCOUNTS_ID + " INTEGER NOT NULL PRIMARY KEY," + | ||
COLUMN_ACCOUNTS_PUBLIC_KEY_RAW + " BLOB NOT NULL," + | ||
COLUMN_ACCOUNTS_LABEL + " TEXT," + | ||
COLUMN_ACCOUNTS_ICON + " TEXT," + | ||
COLUMN_ACCOUNTS_CHAINS + " TEXT," + | ||
COLUMN_ACCOUNTS_FEATURES + " TEXT)"; | ||
|
||
String[] ACCOUNTS_COLUMNS = new String[]{ | ||
COLUMN_ACCOUNTS_ID, | ||
COLUMN_ACCOUNTS_PUBLIC_KEY_RAW, | ||
COLUMN_ACCOUNTS_LABEL, | ||
COLUMN_ACCOUNTS_ICON, | ||
COLUMN_ACCOUNTS_CHAINS, | ||
COLUMN_ACCOUNTS_FEATURES | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,14 +7,20 @@ | |
import android.content.Context; | ||
import android.database.sqlite.SQLiteDatabase; | ||
import android.database.sqlite.SQLiteOpenHelper; | ||
import android.util.Base64; | ||
import android.util.Log; | ||
|
||
import androidx.annotation.NonNull; | ||
|
||
import com.solana.mobilewalletadapter.common.util.JsonPack; | ||
|
||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
/*package*/ class AuthDatabase extends SQLiteOpenHelper { | ||
private static final String TAG = AuthDatabase.class.getSimpleName(); | ||
private static final String DATABASE_NAME_SUFFIX = "-solana-wallet-lib-auth.db"; | ||
private static final int DATABASE_SCHEMA_VERSION = 5; | ||
private static final int DATABASE_SCHEMA_VERSION = 6; | ||
|
||
AuthDatabase(@NonNull Context context, @NonNull AuthIssuerConfig authIssuerConfig) { | ||
super(context, getDatabaseName(authIssuerConfig), null, DATABASE_SCHEMA_VERSION); | ||
|
@@ -29,18 +35,36 @@ public void onConfigure(SQLiteDatabase db) { | |
public void onCreate(SQLiteDatabase db) { | ||
db.execSQL(IdentityRecordSchema.CREATE_TABLE_IDENTITIES); | ||
db.execSQL(AuthorizationsSchema.CREATE_TABLE_AUTHORIZATIONS); | ||
db.execSQL(PublicKeysSchema.CREATE_TABLE_PUBLIC_KEYS); | ||
db.execSQL(AccountRecordsSchema.CREATE_TABLE_ACCOUNTS); | ||
db.execSQL(WalletUriBaseSchema.CREATE_TABLE_WALLET_URI_BASE); | ||
} | ||
|
||
@Override | ||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. onUpgrade is called whenever the database schema version changes (5->6 in this case) |
||
Log.w(TAG, "Old database schema detected; pre-v1.0.0, no DB schema backward compatibility is implemented"); | ||
db.execSQL("DROP TABLE IF EXISTS " + IdentityRecordSchema.TABLE_IDENTITIES); | ||
db.execSQL("DROP TABLE IF EXISTS " + AuthorizationsSchema.TABLE_AUTHORIZATIONS); | ||
db.execSQL("DROP TABLE IF EXISTS " + PublicKeysSchema.TABLE_PUBLIC_KEYS); | ||
db.execSQL("DROP TABLE IF EXISTS " + WalletUriBaseSchema.TABLE_WALLET_URI_BASE); | ||
onCreate(db); | ||
if (oldVersion < 5) { | ||
Log.w(TAG, "Old database schema detected; pre-v1.0.0, no DB schema backward compatibility is implemented"); | ||
db.execSQL("DROP TABLE IF EXISTS " + IdentityRecordSchema.TABLE_IDENTITIES); | ||
db.execSQL("DROP TABLE IF EXISTS " + AuthorizationsSchema.TABLE_AUTHORIZATIONS); | ||
db.execSQL("DROP TABLE IF EXISTS " + PublicKeysSchema.TABLE_PUBLIC_KEYS); | ||
db.execSQL("DROP TABLE IF EXISTS " + WalletUriBaseSchema.TABLE_WALLET_URI_BASE); | ||
onCreate(db); | ||
} else { | ||
Log.w(TAG, "Old database schema detected; pre-v2.0.0, migrating public keys to account records"); | ||
final PublicKeysDao publicKeysDao = new PublicKeysDao(db); | ||
final List<PublicKey> publicKeys = publicKeysDao.getAuthorizedPublicKeys(); | ||
|
||
db.execSQL(AccountRecordsSchema.CREATE_TABLE_ACCOUNTS); | ||
if (!publicKeys.isEmpty()) { | ||
AccountRecordsDao accountRecordsDao = new AccountRecordsDao(db); | ||
for (PublicKey publicKey : publicKeys) { | ||
Log.d(TAG, "migrating Public Key: " + publicKey.accountLabel + " (" + Base64.encodeToString(publicKey.publicKeyRaw, Base64.NO_WRAP) + ")"); | ||
accountRecordsDao.insert(publicKey.publicKeyRaw, publicKey.accountLabel, | ||
null, null, null); | ||
} | ||
} | ||
|
||
db.execSQL("DROP TABLE IF EXISTS " + PublicKeysSchema.TABLE_PUBLIC_KEYS); | ||
} | ||
} | ||
|
||
@NonNull | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summarizing my understanding:
We're changing our "account" model from
PublicKeyRecord
->AccountRecord
because in MWA 2.0, the "account" model needs to be expanded to include chains/features/etc. A public key is now a part of the largerAccountRecord
model.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep exactly. adding new account metadata fields to the database.
having this already done will make it easier to migrate to multi-account too. we still need to store a list of AccountRecords for each authorization, but at least now the AccountRecord itself is there.