Skip to content

Commit

Permalink
auth repo changes only
Browse files Browse the repository at this point in the history
  • Loading branch information
Funkatronics committed Nov 13, 2023
1 parent 05920f3 commit 6ffd522
Show file tree
Hide file tree
Showing 15 changed files with 376 additions and 61 deletions.
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(
"DELETE FROM " + TABLE_ACCOUNTS +
" WHERE " + COLUMN_ACCOUNTS_ID + " NOT IN " +
"(SELECT DISTINCT " + AuthorizationsSchema.COLUMN_AUTHORIZATIONS_ACCOUNT_ID +
" 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
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
Expand Up @@ -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);
Expand All @@ -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) {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,22 @@ public class AuthRecord {
@IntRange(from = 0)
public final long expires;

@Deprecated
@NonNull
public final byte[] publicKey;

@Deprecated
@Nullable
public final String accountLabel;

@NonNull
public final AccountRecord account;

@NonNull
public final String chain;

@NonNull @Deprecated
@Deprecated
@NonNull
public final String cluster;

@NonNull
Expand All @@ -45,38 +51,66 @@ public class AuthRecord {
public final Uri walletUriBase;

@IntRange(from = 1)
/*package*/ final int publicKeyId;
/*package*/ final int accountId;

@IntRange(from = 1)
/*package*/ final int walletUriBaseId;

private boolean mRevoked;

// /*package*/ AuthRecord(@IntRange(from = 1) int id,
// @NonNull IdentityRecord identity,
// @NonNull byte[] publicKey,
// @Nullable String accountLabel,
// @NonNull String chain,
// @NonNull byte[] scope,
// @Nullable Uri walletUriBase,
// @IntRange(from = 1) int publicKeyId,
// @IntRange(from = 1) int walletUriBaseId,
// @IntRange(from = 0) long issued,
// @IntRange(from = 0) long expires) {
// // N.B. This is a package-visibility constructor; these values will all be validated by
// // other components within this package.
// this.id = id;
// this.identity = identity;
// this.publicKey = publicKey;
// this.accountLabel = accountLabel;
// this.chain = chain;
// this.cluster = chain;
// this.scope = scope;
// this.walletUriBase = walletUriBase;
// this.publicKeyId = publicKeyId;
// this.walletUriBaseId = walletUriBaseId;
// this.issued = issued;
// this.expires = expires;
// }

/*package*/ AuthRecord(@IntRange(from = 1) int id,
@NonNull IdentityRecord identity,
@NonNull byte[] publicKey,
@Nullable String accountLabel,
@NonNull AccountRecord account,
@NonNull String chain,
@NonNull byte[] scope,
@Nullable Uri walletUriBase,
@IntRange(from = 1) int publicKeyId,
@IntRange(from = 1) int accountId,
@IntRange(from = 1) int walletUriBaseId,
@IntRange(from = 0) long issued,
@IntRange(from = 0) long expires) {
// N.B. This is a package-visibility constructor; these values will all be validated by
// other components within this package.
this.id = id;
this.identity = identity;
this.publicKey = publicKey;
this.accountLabel = accountLabel;
this.account = account;
this.chain = chain;
this.cluster = chain;
this.scope = scope;
this.walletUriBase = walletUriBase;
this.publicKeyId = publicKeyId;
this.accountId = accountId;
this.walletUriBaseId = walletUriBaseId;
this.issued = issued;
this.expires = expires;

this.publicKey = account.publicKeyRaw;
this.accountLabel = account.accountLabel;
}

public boolean isExpired() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.solana.mobilewalletadapter.walletlib.scenario.AuthorizedAccount;

import java.util.List;

public interface AuthRepository {
Expand All @@ -19,6 +21,7 @@ public interface AuthRepository {
@NonNull
String toAuthToken(@NonNull AuthRecord authRecord);

@Deprecated
@NonNull
AuthRecord issue(@NonNull String name,
@NonNull Uri uri,
Expand All @@ -29,6 +32,15 @@ AuthRecord issue(@NonNull String name,
@Nullable Uri walletUriBase,
@Nullable byte[] scope);

@NonNull
AuthRecord issue(@NonNull String name,
@NonNull Uri uri,
@NonNull Uri relativeIconUri,
@NonNull AuthorizedAccount account,
@NonNull String cluster,
@Nullable Uri walletUriBase,
@Nullable byte[] scope);

@Nullable
AuthRecord reissue(@NonNull AuthRecord authRecord);

Expand Down
Loading

0 comments on commit 6ffd522

Please sign in to comment.