Skip to content

Instantly share code, notes, and snippets.

@guitarrapc
Created January 12, 2019 22:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save guitarrapc/dab9536831ca39c44d90264f2e62ff73 to your computer and use it in GitHub Desktop.
Save guitarrapc/dab9536831ca39c44d90264f2e62ff73 to your computer and use it in GitHub Desktop.
Convert TeamsId's export csv to Bitwarden's import json format.
{
"folders": [
{
"id": "fa2a6822-8595-44e3-a845-269cc3201c65",
"name": "group1"
}
],
"items": []
}
void Main()
{
var outputPath = $@"./bitwarden_import/bitwarden_importdata{DateTime.Now.ToString("yyyyMMdd_HHmmss")}.json";
var teamsidPersonalCsv = @"./teamsid_export/indivisual_records_2019-01-12 20_08_36.csv";
var teamsidOrganizationCsv = @"./teamsid_export/Organization_records_2019-01-12 21_20_10.csv";
var teamsidCsvs = new[] { teamsidPersonalCsv, teamsidOrganizationCsv };
var bitwardenFolderDefinitionJson = @"./bitwarden_export/bitwarden_export_folder_definition.json";
// deserialize folder definitions
BitwardenFolderDefinition DeserializeFolderJson(string path)
{
using (var stream = File.OpenRead(path))
{
var folders = Utf8Json.JsonSerializer.Deserialize<BitwardenFolderDefinition>(stream);
return folders;
}
}
var folderDefinition = DeserializeFolderJson(bitwardenFolderDefinitionJson);
// convert teamsid to bitwarden
var combinedResult = teamsidCsvs.SelectMany(json =>
{
var parser = new CsvParser(json);
var teamsIdDatas = parser.Parse<TeamsId>();
var bitwardenItems = new BitwardenConverter().Convert(teamsIdDatas, folderDefinition, "Socials");
return bitwardenItems;
})
.ToArray();
combinedResult.Dump();
// serialize bitwarden import data
var importData = new BitwardenDefinition
{
folders = folderDefinition.folders,
items = combinedResult,
};
var jsonBytes = Utf8Json.JsonSerializer.Serialize<BitwardenDefinition>(importData);
// output json
File.WriteAllBytes(outputPath, jsonBytes);
}
public class BitwardenConverter
{
private class FieldRecord
{
public string Url { get; set; }
public string Email { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public (string key, string value)[] SecureFields { get; set; }
public (string key, string value)[] Fields { get; set; }
public string Group { get; set; }
}
public BitwardenItem[] Convert(TeamsId[] from, BitwardenFolderDefinition folderDefinition, string defaultGroup = null)
{
var records = from
.Select(record =>
{
// reflection to check fieldXX and get valueXX
var t = record.GetType();
var props = t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var description = GetPropertyValue(record, t, "description");
var note = GetPropertyValue(record, t, "note");
var fieldRecord = ParseFieldRecord(props, t, record, defaultGroup);
// generate typed item
var bitwarden = new BitwardenItem
{
id = Guid.NewGuid().ToString(),
organizationId = null,
collectionIds = null,
type = 1,
name = description,
login = new BitwardenLogin
{
username = fieldRecord.UserName,
password = fieldRecord.Password,
uris = new[] {
new BitwardenUri
{
uri = fieldRecord.Url,
match = null,
}
}
},
favorite = false,
notes = note,
folderId = folderDefinition.GetId(fieldRecord.Group),
};
// initialize fields
var customField = fieldRecord.Fields == null
? Array.Empty<BitwardenField>()
: fieldRecord.Fields.Select(x => new BitwardenField { name = x.key, value = x.value, type = 0 });
var customSecretField = fieldRecord.SecureFields == null
? Array.Empty<BitwardenField>()
: fieldRecord.SecureFields.Select(x => new BitwardenField { name = x.key, value = x.value, type = 1 });
var fields = customField.Concat(customSecretField).ToArray();
if (fields.Any())
{
bitwarden.fields = fields;
}
return bitwarden;
})
.ToArray();
return records;
}
private FieldRecord ParseFieldRecord(PropertyInfo[] props, Type t, TeamsId source, string defaultGroup = null)
{
var fieldRecord = new FieldRecord();
// get FieldXX properties via reflection
var fieldRecords = props
.Where(x => Match(x.Name, @"Field\d+"))
.Where(x => GetPropertyValue(source, t, x.Name) != "")
.Select(x => (key: GetPropertyValue(source, t, x.Name), value: GetPropertyValue(source, t, x.Name.Replace("Field", "Value"))))
.ToArray();
// get field's value and categolize each via field name regex pattern
var secureMemoList = new List<(string, string)>();
var memoList = new List<(string, string)>();
foreach (var record in fieldRecords)
{
switch (record.key)
{
case var _ when Match(record.key, "url") && fieldRecord.Url == null:
fieldRecord.Url = record.value;
break;
case var _ when Match(record.key, "email|e-mail") && fieldRecord.Email == null:
fieldRecord.Email = record.value;
break;
case var _ when Match(record.key, "username") && fieldRecord.UserName == null:
fieldRecord.UserName = record.value;
break;
case var _ when Match(record.key, "password") && fieldRecord.Password == null:
fieldRecord.Password = record.value;
break;
case var _ when Match(record.key, "pass|access|secret|pin|token|enctypt"):
secureMemoList.Add((record.key, record.value));
break;
case var _ when Match(record.key, "group"):
fieldRecord.Group = record.value;
break;
default:
memoList.Add((record.key, record.value));
break;
}
}
// group fallback
if (defaultGroup != null && string.IsNullOrWhiteSpace(fieldRecord.Group))
{
fieldRecord.Group = defaultGroup;
}
fieldRecord.Fields = memoList.ToArray();
fieldRecord.SecureFields = secureMemoList.ToArray();
return fieldRecord;
}
private bool Match(string text, string pattern)
{
return Regex.IsMatch(text, pattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
}
private string GetPropertyValue(TeamsId record, Type t, string propertyField)
{
return (string)t.GetProperty(propertyField).GetValue(record);
}
}
public class CsvParser
{
private readonly string path;
public CsvParser(string path)
{
this.path = path;
}
public T[] Parse<T>() where T : class
{
using (var reader = new StreamReader(path))
using (var csv = new CsvReader(reader))
{
csv.Configuration.MissingFieldFound = null;
var records = csv.GetRecords<T>();
return records.ToArray();
}
}
}
public class BitwardenDefinition
{
public BitwardenFolder[] folders { get; set; }
public BitwardenItem[] items {get;set;}
}
public class BitwardenItem
{
public string id { get; set; }
public string organizationId { get; set; }
public string collectionIds { get; set; }
public bool favorite { get; set; }
public string folderId { get; set; }
// login = 1, card = 3
public int type { get; set; }
public string name { get; set; }
public string notes { get; set; }
public BitwardenField[] fields { get; set; }
public BitwardenLogin login { get; set; }
}
public class BitwardenLogin
{
public BitwardenUri[] uris { get; set; }
public string username { get; set; }
public string password { get; set; }
// authenticator = null (premium only)
public string totp { get; set; }
}
public class BitwardenUri
{
// default = null
public object match { get; set; }
public string uri { get; set; }
}
public class BitwardenField
{
public string name { get; set; }
public string value { get; set; }
// custom = 0, hidden = 1, boolean = 2
public int type { get; set; }
}
public class BitwardenFolderDefinition
{
public BitwardenFolder[] folders { get; set; }
private static readonly string defaultIdName = "Social";
public string GetId(string name)
{
if (folders == null || !folders.Any()) return defaultIdName;
return folders.FirstOrDefault(x => x.name == name)?.id ?? defaultIdName;
}
}
public class BitwardenFolder
{
public string id { get; set; }
public string name { get; set; }
}
public class TeamsId
{
public string description { get; set; }
public string note { get; set; }
public string Field0 { get; set; }
public string Type0 { get; set; }
public string Value0 { get; set; }
public string Field1 { get; set; }
public string Type1 { get; set; }
public string Value1 { get; set; }
public string Field2 { get; set; }
public string Type2 { get; set; }
public string Value2 { get; set; }
public string Field3 { get; set; }
public string Type3 { get; set; }
public string Value3 { get; set; }
public string Field4 { get; set; }
public string Type4 { get; set; }
public string Value4 { get; set; }
public string Field5 { get; set; }
public string Type5 { get; set; }
public string Value5 { get; set; }
public string Field6 { get; set; }
public string Type6 { get; set; }
public string Value6 { get; set; }
public string Field7 { get; set; }
public string Type7 { get; set; }
public string Value7 { get; set; }
public string Field8 { get; set; }
public string Type8 { get; set; }
public string Value8 { get; set; }
public string Field9 { get; set; }
public string Type9 { get; set; }
public string Value9 { get; set; }
public string Field10 { get; set; }
public string Type10 { get; set; }
public string Value10 { get; set; }
public string Field11 { get; set; }
public string Type11 { get; set; }
public string Value11 { get; set; }
public string Field12 { get; set; }
public string Type12 { get; set; }
public string Value12 { get; set; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment