Skip to content

Commit

Permalink
Merge pull request #4 from ThexXTURBOXx/main
Browse files Browse the repository at this point in the history
feat: Add obfuscation option
  • Loading branch information
petercinibulk authored Aug 12, 2022
2 parents dfc26ac + e5a0057 commit 08b6f82
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 3 deletions.
21 changes: 19 additions & 2 deletions packages/envied/lib/src/envied_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,16 @@ class Envied {
/// ```
final String? name;

const Envied({String? path, bool? requireEnvFile, this.name})
/// Allows all the values to be encrypted using a random
/// generated key that is then XOR'd with the encrypted
/// value when being accessed the first time.
/// Please note that the values can not be offered with
/// the const qualifier, but only with final.
/// **Can be overridden by the per-field obfuscate option!**
final bool obfuscate;

const Envied(
{String? path, bool? requireEnvFile, this.name, this.obfuscate = false})
: path = path ?? '.env',
requireEnvFile = requireEnvFile ?? false;
}
Expand All @@ -38,5 +47,13 @@ class EnviedField {
/// The environment variable name specified in the `.env` file to generate for the annotated variable
final String? varName;

const EnviedField({this.varName});
/// Allows this values to be encrypted using a random
/// generated key that is then XOR'd with the encrypted
/// value when being accessed the first time.
/// Please note that the values can not be offered with
/// the const qualifier, but only with final.
/// **Overrides the per-class obfuscate option!**
final bool? obfuscate;

const EnviedField({this.varName, this.obfuscate});
}
13 changes: 13 additions & 0 deletions packages/envied/test/envy_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ void main() {
test('Empty constructor', () {
final envied = Envied();
expect(envied.path, '.env');
expect(envied.requireEnvFile, false);
expect(envied.obfuscate, false);
});

test('Specified path', () {
Expand All @@ -22,17 +24,28 @@ void main() {
final envied = Envied(name: 'Foo');
expect(envied.name, 'Foo');
});

test('Specified obfuscate', () {
final envied = Envied(obfuscate: true);
expect(envied.obfuscate, true);
});
});

group('EnviedField Test Group', () {
test('Empty constructor', () {
final enviedField = EnviedField();
expect(enviedField.varName, null);
expect(enviedField.obfuscate, null);
});

test('Specified path', () {
final enviedField = EnviedField(varName: 'test');
expect(enviedField.varName, 'test');
});

test('Specified obfuscate', () {
final enviedField = EnviedField(obfuscate: true);
expect(enviedField.obfuscate, true);
});
});
}
77 changes: 77 additions & 0 deletions packages/envied_generator/lib/src/generate_line_encrypted.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import 'dart:math';

import 'package:analyzer/dart/element/element.dart';
import 'package:source_gen/source_gen.dart';

/// Generate the line to be used in the generated class.
/// If [value] is `null`, it means the variable definition doesn't exist
/// and an [InvalidGenerationSourceError] will be thrown.
///
/// Since this function also does the type casting,
/// an [InvalidGenerationSourceError] will also be thrown if
/// the type can't be casted, or is not supported.
String generateLineEncrypted(FieldElement field, String? value) {
if (value == null) {
throw InvalidGenerationSourceError(
'Environment variable not found for field `${field.name}`.',
element: field,
);
}

final rand = Random.secure();
final type = field.type.getDisplayString(withNullability: false);
final name = field.name;
final keyName = '_enviedkey$name';

switch (type) {
case "int":
final parsed = int.tryParse(value);
if (parsed == null) {
throw InvalidGenerationSourceError(
'Type `$type` does not align up to value `$value`.',
element: field,
);
} else {
final key = rand.nextInt(1 << 32);
final encValue = parsed ^ key;
return 'static final int $keyName = $key;\n'
'static final int $name = $keyName ^ $encValue;';
}
case "bool":
final lowercaseValue = value.toLowerCase();
if (['true', 'false'].contains(lowercaseValue)) {
final parsed = lowercaseValue == 'true';
final key = rand.nextBool();
final encValue = parsed ^ key;
return 'static final bool $keyName = $key;\n'
'static final bool $name = $keyName ^ $encValue;';
} else {
throw InvalidGenerationSourceError(
'Type `$type` does not align up to value `$value`.',
element: field,
);
}
case "String":
case "dynamic":
final parsed = value.codeUnits;
final key = parsed.map((e) => rand.nextInt(1 << 32)).toList(
growable: false,
);
final encValue = List.generate(parsed.length, (i) => i, growable: false)
.map((i) => parsed[i] ^ key[i])
.toList(growable: false);
final encName = '_envieddata$name';
return 'static const List<int> $keyName = [${key.join(", ")}];\n'
'static const List<int> $encName = [${encValue.join(", ")}];\n'
'static final ${type == 'dynamic' ? '' : 'String'} $name = String.fromCharCodes(\n'
' List.generate($encName.length, (i) => i, growable: false)\n'
' .map((i) => $encName[i] ^ $keyName[i])\n'
' .toList(growable: false),\n'
');';
default:
throw InvalidGenerationSourceError(
'Obfuscated envied can only handle types such as `int`, `bool` and `String`. Type `$type` is not one of them.',
element: field,
);
}
}
10 changes: 9 additions & 1 deletion packages/envied_generator/lib/src/generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:envied/envied.dart';
import 'package:envied_generator/src/generate_line.dart';
import 'package:envied_generator/src/generate_line_encrypted.dart';
import 'package:envied_generator/src/load_envs.dart';
import 'package:source_gen/source_gen.dart';

Expand Down Expand Up @@ -32,6 +33,7 @@ class EnviedGenerator extends GeneratorForAnnotation<Envied> {
requireEnvFile:
annotation.read('requireEnvFile').literalValue as bool? ?? false,
name: annotation.read('name').literalValue as String?,
obfuscate: annotation.read('obfuscate').literalValue as bool,
);

final envs = await loadEnvs(config.path, (error) {
Expand All @@ -48,15 +50,21 @@ class EnviedGenerator extends GeneratorForAnnotation<Envied> {
if (enviedFieldChecker.hasAnnotationOf(fieldEl)) {
DartObject? dartObject = enviedFieldChecker.firstAnnotationOf(fieldEl);
ConstantReader reader = ConstantReader(dartObject);

String varName =
reader.read('varName').literalValue as String? ?? fieldEl.name;

String? varValue;
if (envs.containsKey(varName)) {
varValue = envs[varName];
} else if (Platform.environment.containsKey(varName)) {
varValue = Platform.environment[varName];
}
return generateLine(

final bool obfuscate =
reader.read('obfuscate').literalValue as bool? ?? config.obfuscate;

return (obfuscate ? generateLineEncrypted : generateLine)(
fieldEl,
varValue,
);
Expand Down
30 changes: 30 additions & 0 deletions packages/envied_generator/test/src/generator_tests.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,33 @@ abstract class Env11 {
@EnviedField(varName: 'test_string')
static const String? testString = null;
}

@ShouldGenerate('static const List<int> _enviedkeytestString', contains: true)
@ShouldGenerate('static const List<int> _envieddatatestString', contains: true)
@ShouldGenerate('''
static final String testString = String.fromCharCodes(
List.generate(_envieddatatestString.length, (i) => i, growable: false)
.map((i) => _envieddatatestString[i] ^ _enviedkeytestString[i])
.toList(growable: false),
);
''', contains: true)
@Envied(path: 'test/.env.example', obfuscate: true)
abstract class Env12 {
@EnviedField()
static const String? testString = null;
}

@ShouldGenerate('static const List<int> _enviedkeytestString', contains: true)
@ShouldGenerate('static const List<int> _envieddatatestString', contains: true)
@ShouldGenerate('''
static final String testString = String.fromCharCodes(
List.generate(_envieddatatestString.length, (i) => i, growable: false)
.map((i) => _envieddatatestString[i] ^ _enviedkeytestString[i])
.toList(growable: false),
);
''', contains: true)
@Envied(path: 'test/.env.example', obfuscate: false)
abstract class Env13 {
@EnviedField(obfuscate: true)
static const String? testString = null;
}

0 comments on commit 08b6f82

Please sign in to comment.