Skip to content

Commit

Permalink
feature(apikey): adding apikey to session (#1855)
Browse files Browse the repository at this point in the history
  • Loading branch information
SmartbearYing committed Jul 4, 2023
1 parent 8dafaad commit aa5b60a
Show file tree
Hide file tree
Showing 23 changed files with 360 additions and 43 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## TBD

### Enhancements

* Added `Session.apiKey` so that it can be changed in an `OnSessionCallback`
[#1855](https://github.com/bugsnag/bugsnag-android/pull/1855)

### Bug fixes

* Prevent rare app crash while migrating old `SharedPreferences` data from older versions of `bugsnag-android`
Expand Down
2 changes: 2 additions & 0 deletions bugsnag-android-core/api/bugsnag-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -573,11 +573,13 @@ public abstract interface class com/bugsnag/android/Plugin {
}

public final class com/bugsnag/android/Session : com/bugsnag/android/JsonStream$Streamable, com/bugsnag/android/UserAware {
public fun getApiKey ()Ljava/lang/String;
public fun getApp ()Lcom/bugsnag/android/App;
public fun getDevice ()Lcom/bugsnag/android/Device;
public fun getId ()Ljava/lang/String;
public fun getStartedAt ()Ljava/util/Date;
public fun getUser ()Lcom/bugsnag/android/User;
public fun setApiKey (Ljava/lang/String;)V
public fun setId (Ljava/lang/String;)V
public fun setStartedAt (Ljava/util/Date;)V
public fun setUser (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ static Client generateClient() {

static Session generateSession() {
return new Session("test", new Date(), new User(), false,
new Notifier(), NoopLogger.INSTANCE);
new Notifier(), NoopLogger.INSTANCE, "TEST APIKEY");
}

static Event generateEvent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ void writeLegacyFile(Session session) throws IOException {
*/
@Test
public void testSessionFromFile() throws Exception {
Session payload = new Session(file, new Notifier(), NoopLogger.INSTANCE);
Session payload = new Session(file, new Notifier(), NoopLogger.INSTANCE, "TEST APIKEY");
payload.setApp(generateApp());
payload.setDevice(generateDevice());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public void testSessionFromFile() throws Exception {
session.toStream(stream);
out.flush();

Session payload = new Session(file, new Notifier(), NoopLogger.INSTANCE);
Session payload = new Session(file, new Notifier(), NoopLogger.INSTANCE, "TEST APIKEY");
JSONObject obj = BugsnagTestUtils.streamableToJson(payload);
JSONObject rootNode = obj.getJSONArray("sessions").getJSONObject(0);
assertNotNull(rootNode);
Expand All @@ -71,7 +71,15 @@ public void testSessionFromFile() throws Exception {

@Test
public void testAutoCapturedOverride() throws Exception {
session = new Session("id", new Date(), null, false, new Notifier(), NoopLogger.INSTANCE);
session = new Session(
"id",
new Date(),
null,
false,
new Notifier(),
NoopLogger.INSTANCE,
"TEST APIKEY"
);
assertFalse(session.isAutoCaptured());
session.setAutoCaptured(true);
assertTrue(session.isAutoCaptured());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ internal class BugsnagEventMapper(
// populate session
val sessionMap = map["session"] as? Map<String, Any?>
sessionMap?.let {
event.session = Session(it, logger)
event.session = Session(it, logger, apiKey)
}

// populate threads
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,19 @@ public final class Session implements JsonStream.Streamable, UserAware {
private final AtomicBoolean tracked = new AtomicBoolean(false);
final AtomicBoolean isPaused = new AtomicBoolean(false);

private String apiKey;

static Session copySession(Session session) {
Session copy = new Session(session.id, session.startedAt, session.user,
session.unhandledCount.get(), session.handledCount.get(), session.notifier,
session.logger);
session.logger, session.getApiKey());
copy.tracked.set(session.tracked.get());
copy.autoCaptured.set(session.isAutoCaptured());
return copy;
}

Session(Map<String, Object> map, Logger logger) {
this(null, null, logger);
Session(Map<String, Object> map, Logger logger, String apiKey) {
this(null, null, logger, apiKey);
setId((String) map.get("id"));

String timestamp = (String) map.get("startedAt");
Expand All @@ -61,25 +63,28 @@ static Session copySession(Session session) {
}

Session(String id, Date startedAt, User user, boolean autoCaptured,
Notifier notifier, Logger logger) {
this(null, notifier, logger);
Notifier notifier, Logger logger, String apiKey) {
this(null, notifier, logger, apiKey);
this.id = id;
this.startedAt = new Date(startedAt.getTime());
this.user = user;
this.autoCaptured.set(autoCaptured);
this.apiKey = apiKey;
}

Session(String id, Date startedAt, User user, int unhandledCount, int handledCount,
Notifier notifier, Logger logger) {
this(id, startedAt, user, false, notifier, logger);
Notifier notifier, Logger logger, String apiKey) {
this(id, startedAt, user, false, notifier, logger, apiKey);
this.unhandledCount.set(unhandledCount);
this.handledCount.set(handledCount);
this.tracked.set(true);
this.apiKey = apiKey;
}

Session(File file, Notifier notifier, Logger logger) {
Session(File file, Notifier notifier, Logger logger, String apiKey) {
this.file = file;
this.logger = logger;
this.apiKey = SessionFilenameInfo.findApiKeyInFilename(file, apiKey);
if (notifier != null) {
Notifier copy = new Notifier(notifier.getName(),
notifier.getVersion(), notifier.getUrl());
Expand Down Expand Up @@ -261,4 +266,25 @@ void serializeSessionInfo(@NonNull JsonStream writer) throws IOException {
writer.name("user").value(user);
writer.endObject();
}

/**
* The API key used for session sent to Bugsnag. Even though the API key is set when Bugsnag
* is initialized, you may choose to send certain sessions to a different Bugsnag project.
*/
public void setApiKey(@NonNull String apiKey) {
if (apiKey != null) {
this.apiKey = apiKey;
} else {
logNull("apiKey");
}
}

/**
* The API key used for session sent to Bugsnag. Even though the API key is set when Bugsnag
* is initialized, you may choose to send certain sessions to a different Bugsnag project.
*/
@NonNull
public String getApiKey() {
return apiKey;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.bugsnag.android

import com.bugsnag.android.internal.ImmutableConfig
import java.io.File
import java.util.UUID

Expand All @@ -11,12 +12,13 @@ import java.util.UUID
* timestamp - to sort error reports by time of capture
*/
internal data class SessionFilenameInfo(
var apiKey: String,
val timestamp: Long,
val uuid: String,
val uuid: String
) {

fun encode(): String {
return toFilename(timestamp, uuid)
return toFilename(apiKey, timestamp, uuid)
}

internal companion object {
Expand All @@ -27,29 +29,63 @@ internal data class SessionFilenameInfo(
* Generates a filename for the session in the format
* "[UUID][timestamp]_v2.json"
*/
fun toFilename(timestamp: Long, uuid: String): String {
return "${uuid}${timestamp}_v2.json"
fun toFilename(apiKey: String, timestamp: Long, uuid: String): String {
return "${apiKey}_${uuid}${timestamp}_v3.json"
}

@JvmStatic
fun defaultFilename(): String {
return toFilename(System.currentTimeMillis(), UUID.randomUUID().toString())
fun defaultFilename(
obj: Any,
config: ImmutableConfig
): SessionFilenameInfo {
val sanitizedApiKey = when (obj) {
is Session -> obj.apiKey
else -> config.apiKey
}

return SessionFilenameInfo(
sanitizedApiKey,
System.currentTimeMillis(),
UUID.randomUUID().toString()
)
}

fun fromFile(file: File): SessionFilenameInfo {
fun fromFile(file: File, defaultApiKey: String): SessionFilenameInfo {
return SessionFilenameInfo(
findApiKeyInFilename(file, defaultApiKey),
findTimestampInFilename(file),
findUuidInFilename(file)
)
}

private fun findUuidInFilename(file: File): String {
return file.name.substring(0, uuidLength - 1)
@JvmStatic
fun findUuidInFilename(file: File): String {
var fileName = file.name
if (isFileV3(file)) {
fileName = file.name.substringAfter('_')
}
return fileName.takeIf { it.length >= uuidLength }?.take(uuidLength) ?: ""
}

@JvmStatic
fun findTimestampInFilename(file: File): Long {
return file.name.substring(uuidLength, file.name.indexOf("_")).toLongOrNull() ?: -1
var fileName = file.name
if (isFileV3(file)) {
fileName = file.name.substringAfter('_')
}
return fileName.drop(findUuidInFilename(file).length)
.substringBefore('_')
.toLongOrNull() ?: -1
}

@JvmStatic
fun findApiKeyInFilename(file: File?, defaultApiKey: String): String {
if (file == null || !isFileV3(file)) {
return defaultApiKey
}
return file.name.substringBefore('_').takeUnless { it.isEmpty() } ?: defaultApiKey
}

internal fun isFileV3(file: File): Boolean = file.name.endsWith("_v3.json")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
class SessionStore extends FileStore {

private final ImmutableConfig config;
static final Comparator<File> SESSION_COMPARATOR = new Comparator<File>() {
@Override
public int compare(File lhs, File rhs) {
Expand All @@ -43,12 +44,15 @@ public int compare(File lhs, File rhs) {
SESSION_COMPARATOR,
logger,
delegate);
this.config = config;
}

@NonNull
@Override
String getFilename(Object object) {
return SessionFilenameInfo.defaultFilename();
SessionFilenameInfo sessionInfo
= SessionFilenameInfo.defaultFilename(object, config);
return sessionInfo.encode();
}

public boolean isTooOld(File file) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@ Session startNewSession(@NonNull Date date, @Nullable User user,
return null;
}
String id = UUID.randomUUID().toString();
Session session = new Session(id, date, user, autoCaptured, client.getNotifier(), logger);
Session session = new Session(
id, date, user, autoCaptured,
client.getNotifier(), logger, configuration.getApiKey()
);
if (trackSessionIfNeeded(session)) {
return session;
} else {
Expand Down Expand Up @@ -157,7 +160,7 @@ Session registerExistingSession(@Nullable Date date, @Nullable String sessionId,
Session session = null;
if (date != null && sessionId != null) {
session = new Session(sessionId, date, user, unhandledCount, handledCount,
client.getNotifier(), logger);
client.getNotifier(), logger, configuration.getApiKey());
notifySessionStartObserver(session);
} else {
updateState(StateEvent.PauseSession.INSTANCE);
Expand Down Expand Up @@ -256,7 +259,9 @@ void flushStoredSessions() {

void flushStoredSession(File storedFile) {
logger.d("SessionTracker#flushStoredSession() - attempting delivery");
Session payload = new Session(storedFile, client.getNotifier(), logger);
Session payload = new Session(
storedFile, client.getNotifier(), logger, configuration.getApiKey()
);

if (!payload.isV2Payload()) { // collect data here
payload.setApp(client.getAppDataCollector().generateApp());
Expand Down Expand Up @@ -330,7 +335,7 @@ void deliverInMemorySession(Session session) {
}

DeliveryStatus deliverSessionPayload(Session payload) {
DeliveryParams params = configuration.getSessionApiDeliveryParams();
DeliveryParams params = configuration.getSessionApiDeliveryParams(payload);
Delivery delivery = configuration.getDelivery();
return delivery.deliver(payload, params);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.bugsnag.android.EventPayload
import com.bugsnag.android.Logger
import com.bugsnag.android.ManifestConfigLoader.Companion.BUILD_UUID
import com.bugsnag.android.NoopLogger
import com.bugsnag.android.Session
import com.bugsnag.android.Telemetry
import com.bugsnag.android.ThreadSendPolicy
import com.bugsnag.android.errorApiHeaders
Expand Down Expand Up @@ -65,8 +66,8 @@ data class ImmutableConfig(
DeliveryParams(endpoints.notify, errorApiHeaders(payload))

@JvmName("getSessionApiDeliveryParams")
internal fun getSessionApiDeliveryParams() =
DeliveryParams(endpoints.sessions, sessionApiHeaders(apiKey))
internal fun getSessionApiDeliveryParams(session: Session) =
DeliveryParams(endpoints.sessions, sessionApiHeaders(session.apiKey))

/**
* Returns whether the given throwable should be discarded
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ static EventPayload generateEventPayload(ImmutableConfig config) {

static Session generateSession() {
return new Session("test", new Date(), new User(), false,
new Notifier(), NoopLogger.INSTANCE);
new Notifier(), NoopLogger.INSTANCE, "BUGSNAG_API_KEY");
}

static Event generateEvent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ internal class DeliveryDelegateTest {
@Mock
lateinit var eventStore: EventStore

private val apiKey = "BUGSNAG_API_KEY"
private val notifier = Notifier()
val config = generateImmutableConfig()
val callbackState = CallbackState()
Expand All @@ -40,7 +41,7 @@ internal class DeliveryDelegateTest {
notifier,
BackgroundTaskService()
)
event.session = Session("123", Date(), User(null, null, null), false, notifier, NoopLogger)
event.session = Session("123", Date(), User(null, null, null), false, notifier, NoopLogger, apiKey)
}

@Test
Expand All @@ -59,6 +60,8 @@ internal class DeliveryDelegateTest {
// check session count incremented
assertEquals(1, event.session!!.unhandledCount)
assertEquals(0, event.session!!.handledCount)

assertEquals("BUGSNAG_API_KEY", event.session!!.apiKey)
}

@Test
Expand All @@ -67,7 +70,7 @@ internal class DeliveryDelegateTest {
SeverityReason.REASON_HANDLED_EXCEPTION
)
val event = Event(RuntimeException("Whoops!"), config, state, NoopLogger)
event.session = Session("123", Date(), User(null, null, null), false, notifier, NoopLogger)
event.session = Session("123", Date(), User(null, null, null), false, notifier, NoopLogger, apiKey)

var msg: StateEvent.NotifyHandled? = null
deliveryDelegate.addObserver(
Expand Down

0 comments on commit aa5b60a

Please sign in to comment.