1package teams
2
3import (
4	"encoding/json"
5	"testing"
6
7	"golang.org/x/net/context"
8
9	"github.com/davecgh/go-spew/spew"
10	"github.com/keybase/client/go/kbtest"
11	"github.com/keybase/client/go/libkb"
12	"github.com/keybase/client/go/protocol/keybase1"
13	"github.com/keybase/go-codec/codec"
14	"github.com/stretchr/testify/require"
15)
16
17// A chain with a stubbed link
18// Output of `rotate_root_team_key` test in team_integration.iced
19const teamChain1 = `
20{"status":{"code":0,"name":"OK"},"chain":[{"seqno":1,"sig":"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEgewPUgFaXvAjHWBC4BnTzSdMT3izYy89VX+4rjh2NlMkKp3BheWxvYWTEJ5UCAcDEIL95/rs8CFK3VSp4W4nYmxAdatMKJf+BB1xWH3eB/K7vIaNzaWfEQAez3e1PlPXJUg2vITA8/ZKeIKBp67DQQaNSKchzpPaMN8BFtvlY2qQStmk5Jn9mcN7m5x7xGBvRLksLkz4fOQSoc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==","payload_json":"{\"body\":{\"key\":{\"eldest_kid\":\"01207b03d4805697bc08c75810b80674f349d313de2cd8cbcf555fee2b8e1d8d94c90a\",\"host\":\"keybase.io\",\"kid\":\"01207b03d4805697bc08c75810b80674f349d313de2cd8cbcf555fee2b8e1d8d94c90a\",\"uid\":\"5bf82de4331b50b32cbbcfeadc2f3119\",\"username\":\"d_af8eac8c\"},\"team\":{\"id\":\"64d27654bef64bdb3d78d84f186c4224\",\"members\":{\"admin\":[\"53e315afb4b419931b0a6a1eaa09e219\"],\"owner\":[\"5bf82de4331b50b32cbbcfeadc2f3119\"],\"reader\":[\"13e18aeafa4df6c94bf6af7d7bb98d19\"],\"writer\":[\"4bf92804c02fb7d2cd36a6d420d6f619\"]},\"name\":\"t_9d6d1e37\",\"per_team_key\":{\"encryption_kid\":\"0121bf2085a5f1b4f8e0ad5095fb29ae65f7e52a4fa5d9bc90757515c7dd860767020a\",\"generation\":1,\"reverse_sig\":\"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEgYr3LeU54Mu4AdjeZ3bG+7c0yEL51p2dfHxneCIxTVPEKp3BheWxvYWTFA3R7ImJvZHkiOnsia2V5Ijp7ImVsZGVzdF9raWQiOiIwMTIwN2IwM2Q0ODA1Njk3YmMwOGM3NTgxMGI4MDY3NGYzNDlkMzEzZGUyY2Q4Y2JjZjU1NWZlZTJiOGUxZDhkOTRjOTBhIiwiaG9zdCI6ImtleWJhc2UuaW8iLCJraWQiOiIwMTIwN2IwM2Q0ODA1Njk3YmMwOGM3NTgxMGI4MDY3NGYzNDlkMzEzZGUyY2Q4Y2JjZjU1NWZlZTJiOGUxZDhkOTRjOTBhIiwidWlkIjoiNWJmODJkZTQzMzFiNTBiMzJjYmJjZmVhZGMyZjMxMTkiLCJ1c2VybmFtZSI6ImRfYWY4ZWFjOGMifSwidGVhbSI6eyJpZCI6IjY0ZDI3NjU0YmVmNjRiZGIzZDc4ZDg0ZjE4NmM0MjI0IiwibWVtYmVycyI6eyJhZG1pbiI6WyI1M2UzMTVhZmI0YjQxOTkzMWIwYTZhMWVhYTA5ZTIxOSJdLCJvd25lciI6WyI1YmY4MmRlNDMzMWI1MGIzMmNiYmNmZWFkYzJmMzExOSJdLCJyZWFkZXIiOlsiMTNlMThhZWFmYTRkZjZjOTRiZjZhZjdkN2JiOThkMTkiXSwid3JpdGVyIjpbIjRiZjkyODA0YzAyZmI3ZDJjZDM2YTZkNDIwZDZmNjE5Il19LCJuYW1lIjoidF85ZDZkMWUzNyIsInBlcl90ZWFtX2tleSI6eyJlbmNyeXB0aW9uX2tpZCI6IjAxMjFiZjIwODVhNWYxYjRmOGUwYWQ1MDk1ZmIyOWFlNjVmN2U1MmE0ZmE1ZDliYzkwNzU3NTE1YzdkZDg2MDc2NzAyMGEiLCJnZW5lcmF0aW9uIjoxLCJyZXZlcnNlX3NpZyI6bnVsbCwic2lnbmluZ19raWQiOiIwMTIwNjJiZGNiNzk0ZTc4MzJlZTAwNzYzNzk5ZGRiMWJlZWRjZDMyMTBiZTc1YTc2NzVmMWYxOWRlMDg4YzUzNTRmMTBhIn19LCJ0eXBlIjoidGVhbS5yb290IiwidmVyc2lvbiI6Mn0sImN0aW1lIjoxNDk3MjM4NTMyLCJleHBpcmVfaW4iOjE1NzY4MDAwMCwicHJldiI6bnVsbCwic2VxX3R5cGUiOjMsInNlcW5vIjoxLCJ0YWciOiJzaWduYXR1cmUifaNzaWfEQLrzfIl+c/rDlaTL9hW5emLJSJOoyhWw0gKnShh4v5FzX0tunexOId0U87etEgT1P+uJ6KYCSQTh8ZdCmDWJ7Q6oc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==\",\"signing_kid\":\"012062bdcb794e7832ee00763799ddb1beedcd3210be75a7675f1f19de088c5354f10a\"}},\"type\":\"team.root\",\"version\":2},\"ctime\":1497238532,\"expire_in\":157680000,\"prev\":null,\"seq_type\":3,\"seqno\":1,\"tag\":\"signature\"}","version":2,"uid":"5bf82de4331b50b32cbbcfeadc2f3119"},{"seqno":2,"sig":"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEgewPUgFaXvAjHWBC4BnTzSdMT3izYy89VX+4rjh2NlMkKp3BheWxvYWTESJUCAsQgYst2GtNGo9KL4e7RB8NVSKLdb63pIZo4WRhB9i1YbP7EICPQdqUmdeZU/gRJXd5+gUP8HSxtn4xMeZ7lssS3Pm+8IqNzaWfEQHkBp46skYqz62rMjoxZGq4HVjhJHCS4zYjmrMDhQQrl3fu76HeRqTeuLWCh0741OyvwXTjGNY7oCJiT5YvZSw+oc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==","version":2,"uid":"5bf82de4331b50b32cbbcfeadc2f3119"},{"seqno":3,"sig":"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEgTqfhtGmmOGhixMw9YsHIrmrk0txnZBM4A/hqS1gWbzUKp3BheWxvYWTESJUCA8Qgg2TPT1Vbq+1yiOlYEFHqQYcccB4W6bevJFRK1jc1ov7EIHbADJMePlGQ1+JCJe9AQiftKeKwAIKLYLC1pPvcWyZmJKNzaWfEQPNVaBljU0mSO/27FfQDZiNaNYfbZ+lG1QF2WaOoUBgtChMxEek+3jKWTGkfWSvjL+MynM8ve+egRteBY8jhoQioc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==","payload_json":"{\"body\":{\"key\":{\"eldest_kid\":\"01204ea7e1b469a6386862c4cc3d62c1c8ae6ae4d2dc6764133803f86a4b58166f350a\",\"host\":\"keybase.io\",\"kid\":\"01204ea7e1b469a6386862c4cc3d62c1c8ae6ae4d2dc6764133803f86a4b58166f350a\",\"uid\":\"4bf92804c02fb7d2cd36a6d420d6f619\",\"username\":\"b_7804991a\"},\"team\":{\"id\":\"64d27654bef64bdb3d78d84f186c4224\",\"per_team_key\":{\"encryption_kid\":\"0121e2511cbfb0418187a8e19183a1cd92637bc83fe116d1eb8984f52394495b5f120a\",\"generation\":2,\"reverse_sig\":\"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEggC9V7V4U9hxzCHG16Z1jcFT/f1ugwbLMUrFU47r4CgAKp3BheWxvYWTFAuJ7ImJvZHkiOnsia2V5Ijp7ImVsZGVzdF9raWQiOiIwMTIwNGVhN2UxYjQ2OWE2Mzg2ODYyYzRjYzNkNjJjMWM4YWU2YWU0ZDJkYzY3NjQxMzM4MDNmODZhNGI1ODE2NmYzNTBhIiwiaG9zdCI6ImtleWJhc2UuaW8iLCJraWQiOiIwMTIwNGVhN2UxYjQ2OWE2Mzg2ODYyYzRjYzNkNjJjMWM4YWU2YWU0ZDJkYzY3NjQxMzM4MDNmODZhNGI1ODE2NmYzNTBhIiwidWlkIjoiNGJmOTI4MDRjMDJmYjdkMmNkMzZhNmQ0MjBkNmY2MTkiLCJ1c2VybmFtZSI6ImJfNzgwNDk5MWEifSwidGVhbSI6eyJpZCI6IjY0ZDI3NjU0YmVmNjRiZGIzZDc4ZDg0ZjE4NmM0MjI0IiwicGVyX3RlYW1fa2V5Ijp7ImVuY3J5cHRpb25fa2lkIjoiMDEyMWUyNTExY2JmYjA0MTgxODdhOGUxOTE4M2ExY2Q5MjYzN2JjODNmZTExNmQxZWI4OTg0ZjUyMzk0NDk1YjVmMTIwYSIsImdlbmVyYXRpb24iOjIsInJldmVyc2Vfc2lnIjpudWxsLCJzaWduaW5nX2tpZCI6IjAxMjA4MDJmNTVlZDVlMTRmNjFjNzMwODcxYjVlOTlkNjM3MDU0ZmY3ZjViYTBjMWIyY2M1MmIxNTRlM2JhZjgwYTAwMGEifX0sInR5cGUiOiJ0ZWFtLnJvdGF0ZV9rZXkiLCJ2ZXJzaW9uIjoyfSwiY3RpbWUiOjE0OTcyMzg1MzUsImV4cGlyZV9pbiI6MTU3NjgwMDAwLCJwcmV2IjoiODM2NGNmNGY1NTViYWJlZDcyODhlOTU4MTA1MWVhNDE4NzFjNzAxZTE2ZTliN2FmMjQ1NDRhZDYzNzM1YTJmZSIsInNlcV90eXBlIjozLCJzZXFubyI6MywidGFnIjoic2lnbmF0dXJlIn2jc2lnxECfcafw2CoIzFKtmN2nt3A28wYS7clrmEZvjLEziNmoWy525gvyxJEHiENfxQ5kt9Uxb0cCDChlktHvz23my6QAqHNpZ190eXBlIKN0YWfNAgKndmVyc2lvbgE=\",\"signing_kid\":\"0120802f55ed5e14f61c730871b5e99d637054ff7f5ba0c1b2cc52b154e3baf80a000a\"}},\"type\":\"team.rotate_key\",\"version\":2},\"ctime\":1497238535,\"expire_in\":157680000,\"prev\":\"8364cf4f555babed7288e9581051ea41871c701e16e9b7af24544ad63735a2fe\",\"seq_type\":3,\"seqno\":3,\"tag\":\"signature\"}","version":2,"uid":"4bf92804c02fb7d2cd36a6d420d6f619"}],"box":{"nonce":"5VPBQypqrcLiuW1i6fVROxmuBQUAAAAD","sender_kid":"012112f29aa42e14053a057a790a198a5b7fb25512c8458b4b32d7bbd04c0d52093b0a","generation":2,"ctext":"HgbVY5cswOvxu9PMOP75NdYqftGtgenRABKtjHgervLK6/oaF1vTW2U9vt+0JixD","per_user_key_seqno":3},"prevs":{"2":"9301c418e553c1432a6aadc2e2b96d62e9f5513b19ae050500000000c4304e966d60d2ccee5433fa1cf08f11de02b0d4e749798925d925896edc6c0b12288a975db3b29f6efed165b38775b64d42"},"reader_key_masks":[{"mask":"gKBbFV3J3L0j/gFtruyCKpZHf7Y837Q2ezFtrAK6xIE=","application":1,"generation":1},{"mask":"ZYdXI0jfXscYwgXO3J3A1T9YRJ+GlkNvtEZJ4nvmKbk=","application":2,"generation":1},{"mask":"Dq4iXhUs9BxX1DHYP7wE/vFOpG4SABwzHZRQeavnKjM=","application":1,"generation":2},{"mask":"AEedcZX7wZyvyAOyc9EQINr3MyqbKMKXLfZVHHZqz7U=","application":2,"generation":2}],"id":"64d27654bef64bdb3d78d84f186c4224","name":{"parts":["t_9d6d1e37"]},"csrf_token":"lgHZIDRiZjkyODA0YzAyZmI3ZDJjZDM2YTZkNDIwZDZmNjE5zlk+C/7OAAFRgMDEIFLYZSdoOin9JRKgyjN8z/JMVQ4Az3O1ZcUyT43DTlXV"}
21`
22
23// A chain with a change_membership link, generated via: `change_membership_promote_to_writer_happy_path`
24const teamChain2 = `
25{"status":{"code":0,"name":"OK"},"chain":[{"seqno":1,"sig":"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEg0n4BukVK2MAN0FR3OKg2LjyIRb927JAVdNeKFKxDRTsKp3BheWxvYWTEJ5UCAcDEIKkpvmNu1RlxqBwYtqk4eG/jKv7KD/GllQ23k/Dd6VB1IaNzaWfEQCNHcFQwf9uDBjhzyXDydfUn/7QK1MgC4T2xW/4hywf9vXdVLJ3sPWb+gvk2ZoPCdiwmiAl3CCMiUIuaZrEIawSoc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==","payload_json":"{\"body\":{\"key\":{\"eldest_kid\":\"0120d27e01ba454ad8c00dd0547738a8362e3c8845bf76ec901574d78a14ac43453b0a\",\"host\":\"keybase.io\",\"kid\":\"0120d27e01ba454ad8c00dd0547738a8362e3c8845bf76ec901574d78a14ac43453b0a\",\"uid\":\"99759da4f968b16121ece44652f01a19\",\"username\":\"d_6d4e925d\"},\"team\":{\"id\":\"5d2c9db17c2309bf818ceefece77b624\",\"members\":{\"admin\":[\"b720a648e02b99c10d50de0c4f265419\"],\"owner\":[\"99759da4f968b16121ece44652f01a19\"],\"reader\":[\"c8f463c79c83fec675c398b6aa3fa719\"],\"writer\":[\"921f0e1f2632277cc1fa6600e0906819\"]},\"name\":\"t_bfaadb41\",\"per_team_key\":{\"encryption_kid\":\"01218ca00b08b4ee5729d957cf14155098b74199588bb5eee778ad1eae58bce26c370a\",\"generation\":1,\"reverse_sig\":\"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEgiyRSCOiVHN56vT9eusVXlCTlHtCVH+FKyKeZbJzPiuUKp3BheWxvYWTFA3R7ImJvZHkiOnsia2V5Ijp7ImVsZGVzdF9raWQiOiIwMTIwZDI3ZTAxYmE0NTRhZDhjMDBkZDA1NDc3MzhhODM2MmUzYzg4NDViZjc2ZWM5MDE1NzRkNzhhMTRhYzQzNDUzYjBhIiwiaG9zdCI6ImtleWJhc2UuaW8iLCJraWQiOiIwMTIwZDI3ZTAxYmE0NTRhZDhjMDBkZDA1NDc3MzhhODM2MmUzYzg4NDViZjc2ZWM5MDE1NzRkNzhhMTRhYzQzNDUzYjBhIiwidWlkIjoiOTk3NTlkYTRmOTY4YjE2MTIxZWNlNDQ2NTJmMDFhMTkiLCJ1c2VybmFtZSI6ImRfNmQ0ZTkyNWQifSwidGVhbSI6eyJpZCI6IjVkMmM5ZGIxN2MyMzA5YmY4MThjZWVmZWNlNzdiNjI0IiwibWVtYmVycyI6eyJhZG1pbiI6WyJiNzIwYTY0OGUwMmI5OWMxMGQ1MGRlMGM0ZjI2NTQxOSJdLCJvd25lciI6WyI5OTc1OWRhNGY5NjhiMTYxMjFlY2U0NDY1MmYwMWExOSJdLCJyZWFkZXIiOlsiYzhmNDYzYzc5YzgzZmVjNjc1YzM5OGI2YWEzZmE3MTkiXSwid3JpdGVyIjpbIjkyMWYwZTFmMjYzMjI3N2NjMWZhNjYwMGUwOTA2ODE5Il19LCJuYW1lIjoidF9iZmFhZGI0MSIsInBlcl90ZWFtX2tleSI6eyJlbmNyeXB0aW9uX2tpZCI6IjAxMjE4Y2EwMGIwOGI0ZWU1NzI5ZDk1N2NmMTQxNTUwOThiNzQxOTk1ODhiYjVlZWU3NzhhZDFlYWU1OGJjZTI2YzM3MGEiLCJnZW5lcmF0aW9uIjoxLCJyZXZlcnNlX3NpZyI6bnVsbCwic2lnbmluZ19raWQiOiIwMTIwOGIyNDUyMDhlODk1MWNkZTdhYmQzZjVlYmFjNTU3OTQyNGU1MWVkMDk1MWZlMTRhYzhhNzk5NmM5Y2NmOGFlNTBhIn19LCJ0eXBlIjoidGVhbS5yb290IiwidmVyc2lvbiI6Mn0sImN0aW1lIjoxNDk3MjM5NTY2LCJleHBpcmVfaW4iOjE1NzY4MDAwMCwicHJldiI6bnVsbCwic2VxX3R5cGUiOjMsInNlcW5vIjoxLCJ0YWciOiJzaWduYXR1cmUifaNzaWfEQIhIqjwqQQFY8WglSLtvZu1hpncnMutA/jLaFmQJWcjdoMilr4ttLg3wlxKm6m+zWJC0Y9tiYBeHqGLYj5Mmkgaoc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==\",\"signing_kid\":\"01208b245208e8951cde7abd3f5ebac5579424e51ed0951fe14ac8a7996c9ccf8ae50a\"}},\"type\":\"team.root\",\"version\":2},\"ctime\":1497239566,\"expire_in\":157680000,\"prev\":null,\"seq_type\":3,\"seqno\":1,\"tag\":\"signature\"}","version":2,"uid":"99759da4f968b16121ece44652f01a19"},{"seqno":2,"sig":"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEg0n4BukVK2MAN0FR3OKg2LjyIRb927JAVdNeKFKxDRTsKp3BheWxvYWTESJUCAsQgaCisMgGb2MAyfqV80hthgsO25NQIAYK7ARn5pjCDP1HEIKj3ZEpkyhQYH2mYtiHiXQVe7V5D4qgNwupJ5Nr/2nhcI6NzaWfEQNKql5dvPsQJK+pZKVGiLWS723t9SgaaDFx6NicXJzOM0VnLHKnql50wUcY/KsJOCqUIpKvJmNj6ogbwN/ljTwaoc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==","payload_json":"{\"body\":{\"key\":{\"eldest_kid\":\"0120d27e01ba454ad8c00dd0547738a8362e3c8845bf76ec901574d78a14ac43453b0a\",\"host\":\"keybase.io\",\"kid\":\"0120d27e01ba454ad8c00dd0547738a8362e3c8845bf76ec901574d78a14ac43453b0a\",\"uid\":\"99759da4f968b16121ece44652f01a19\",\"username\":\"d_6d4e925d\"},\"team\":{\"id\":\"5d2c9db17c2309bf818ceefece77b624\",\"members\":{\"writer\":[\"c8f463c79c83fec675c398b6aa3fa719\"]}},\"type\":\"team.change_membership\",\"version\":2},\"ctime\":1497239567,\"expire_in\":157680000,\"prev\":\"6828ac32019bd8c0327ea57cd21b6182c3b6e4d4080182bb0119f9a630833f51\",\"seq_type\":3,\"seqno\":2,\"tag\":\"signature\"}","version":2,"uid":"99759da4f968b16121ece44652f01a19"}],"box":{"nonce":"hdDTz9Scb6dWbe+BZsyZaXx76aQAAAAB","sender_kid":"0121a4f15b1009430ff69224c0c659eba66d61ca743b0d661a79075c8ca63a6e535d0a","generation":1,"ctext":"4Dz4i3Wzdj/BdH/kYCXvFnl1XKCqhxc58Z53j9VDloy/KG2ZzH204Sw5Q1xkzFkV","per_user_key_seqno":3},"prevs":{},"reader_key_masks":[{"mask":"tewSORjnVoyuHKR6ztsND+MNP2Pp9skEEEYmMBIQ5cY=","application":1,"generation":1},{"mask":"x7ZvY+WfK6WaJOCulxfOpdLgBEyuzSc8KgyIQGxT2uQ=","application":2,"generation":1}],"id":"5d2c9db17c2309bf818ceefece77b624","name":{"parts":["t_bfaadb41"]},"csrf_token":"lgHZIDk5NzU5ZGE0Zjk2OGIxNjEyMWVjZTQ0NjUyZjAxYTE5zlk+EA3OAAFRgMDEIKCIaEA5GV5wng89rpM0UlLiqxcOyNIVk8KdsEjQpmAm"}
26`
27
28// A chain with an invite and a cancelation, as generated via: `cancel_invite_happy_path`
29const teamChainWithInvites = `
30{"status":{"code":0,"name":"OK"},"chain":[{"seqno":1,"sig":"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEglAdcaNPA48Z7qTCfxDuSWqaFhsz2wnhzP4XAf5xi8cEKp3BheWxvYWTEJ5UCAcDEIEi+9D4UalDtumiKbRRbMIx6b3w7pJ/wE/+P50FWPLiwIaNzaWfEQGBtlFet2J8qB9rUjGAFU8w8WBECB2zn5Tr5g1PbXJK3q6uG0GPWbUFrsY8DCopgKcucthrz0/58ehnwpvbwjAyoc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==","payload_json":"{\"body\":{\"key\":{\"eldest_kid\":\"012094075c68d3c0e3c67ba9309fc43b925aa68586ccf6c278733f85c07f9c62f1c10a\",\"host\":\"keybase.io\",\"kid\":\"012094075c68d3c0e3c67ba9309fc43b925aa68586ccf6c278733f85c07f9c62f1c10a\",\"uid\":\"934b8105dc1bd94d8be109b08ef5c119\",\"username\":\"a66714531\"},\"merkle_root\":{\"ctime\":1499358688,\"hash\":\"8d60b173eadcb63f20fd6089fed7c1f4031b805e3892ef6c4be432737e6470ff9d1dbd48d6e81d33dd6480b56b6451775c4d266ba2f12bacc00a758f95614844\",\"hash_meta\":\"98d75dd2b3b27dba1599babdfdbd0b7cac312c2c9b0d96ad438277bef5454566\",\"seqno\":332458},\"team\":{\"id\":\"7ebe4dbfe458fde9a5e2ffdc4eb96a24\",\"members\":{\"owner\":[\"934b8105dc1bd94d8be109b08ef5c119\"],\"writer\":[\"d71179f6b33171b72695ee23a44ecc19\"]},\"name\":\"t_efc95e26\",\"per_team_key\":{\"encryption_kid\":\"0121c41ecfba3ec588c029a096adc78f9419c935059c8a694ac30c0c7f30b499485e0a\",\"generation\":1,\"reverse_sig\":\"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEg/jl9IghbR8CHSTUp2sYPIBLOEMa4b9enId/u5nD/OMoKp3BheWxvYWTFBCN7ImJvZHkiOnsia2V5Ijp7ImVsZGVzdF9raWQiOiIwMTIwOTQwNzVjNjhkM2MwZTNjNjdiYTkzMDlmYzQzYjkyNWFhNjg1ODZjY2Y2YzI3ODczM2Y4NWMwN2Y5YzYyZjFjMTBhIiwiaG9zdCI6ImtleWJhc2UuaW8iLCJraWQiOiIwMTIwOTQwNzVjNjhkM2MwZTNjNjdiYTkzMDlmYzQzYjkyNWFhNjg1ODZjY2Y2YzI3ODczM2Y4NWMwN2Y5YzYyZjFjMTBhIiwidWlkIjoiOTM0YjgxMDVkYzFiZDk0ZDhiZTEwOWIwOGVmNWMxMTkiLCJ1c2VybmFtZSI6ImE2NjcxNDUzMSJ9LCJtZXJrbGVfcm9vdCI6eyJjdGltZSI6MTQ5OTM1ODY4OCwiaGFzaCI6IjhkNjBiMTczZWFkY2I2M2YyMGZkNjA4OWZlZDdjMWY0MDMxYjgwNWUzODkyZWY2YzRiZTQzMjczN2U2NDcwZmY5ZDFkYmQ0OGQ2ZTgxZDMzZGQ2NDgwYjU2YjY0NTE3NzVjNGQyNjZiYTJmMTJiYWNjMDBhNzU4Zjk1NjE0ODQ0IiwiaGFzaF9tZXRhIjoiOThkNzVkZDJiM2IyN2RiYTE1OTliYWJkZmRiZDBiN2NhYzMxMmMyYzliMGQ5NmFkNDM4Mjc3YmVmNTQ1NDU2NiIsInNlcW5vIjozMzI0NTh9LCJ0ZWFtIjp7ImlkIjoiN2ViZTRkYmZlNDU4ZmRlOWE1ZTJmZmRjNGViOTZhMjQiLCJtZW1iZXJzIjp7Im93bmVyIjpbIjkzNGI4MTA1ZGMxYmQ5NGQ4YmUxMDliMDhlZjVjMTE5Il0sIndyaXRlciI6WyJkNzExNzlmNmIzMzE3MWI3MjY5NWVlMjNhNDRlY2MxOSJdfSwibmFtZSI6InRfZWZjOTVlMjYiLCJwZXJfdGVhbV9rZXkiOnsiZW5jcnlwdGlvbl9raWQiOiIwMTIxYzQxZWNmYmEzZWM1ODhjMDI5YTA5NmFkYzc4Zjk0MTljOTM1MDU5YzhhNjk0YWMzMGMwYzdmMzBiNDk5NDg1ZTBhIiwiZ2VuZXJhdGlvbiI6MSwicmV2ZXJzZV9zaWciOm51bGwsInNpZ25pbmdfa2lkIjoiMDEyMGZlMzk3ZDIyMDg1YjQ3YzA4NzQ5MzUyOWRhYzYwZjIwMTJjZTEwYzZiODZmZDdhNzIxZGZlZWU2NzBmZjM4Y2EwYSJ9fSwidHlwZSI6InRlYW0ucm9vdCIsInZlcnNpb24iOjJ9LCJjdGltZSI6MTQ5OTM1ODY4OCwiZXhwaXJlX2luIjoxNTc2ODAwMDAsInByZXYiOm51bGwsInNlcV90eXBlIjozLCJzZXFubyI6MSwidGFnIjoic2lnbmF0dXJlIn2jc2lnxEDB7QWbAjIcXgVqq6GnGXNK/IbwNMDq9EVTOfUGQYk63CSRL3zLKCmQh0s3qdPiT5SXNipSBXdX+pvp8/cK1wILqHNpZ190eXBlIKN0YWfNAgKndmVyc2lvbgE=\",\"signing_kid\":\"0120fe397d22085b47c087493529dac60f2012ce10c6b86fd7a721dfeee670ff38ca0a\"}},\"type\":\"team.root\",\"version\":2},\"ctime\":1499358688,\"expire_in\":157680000,\"prev\":null,\"seq_type\":3,\"seqno\":1,\"tag\":\"signature\"}","version":2,"uid":"934b8105dc1bd94d8be109b08ef5c119"},{"seqno":2,"sig":"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEglAdcaNPA48Z7qTCfxDuSWqaFhsz2wnhzP4XAf5xi8cEKp3BheWxvYWTESJUCAsQg7vTbqMWQF4drUC6qX8AT4D8QyRBuebus07XiQT9rdD/EIKpkXqyCDdvYFx8b3A0ROEjwmgS26msj5nVkjPPWpwrFKKNzaWfEQNVi98WQKl9JvPfQxAU8QtxIajxKgwUVwKMcpRoAlfViMXHizVl7EQbnPtAL7AfE26HGd2pVFzCJgyP19Smzpgqoc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==","payload_json":"{\"body\":{\"key\":{\"eldest_kid\":\"012094075c68d3c0e3c67ba9309fc43b925aa68586ccf6c278733f85c07f9c62f1c10a\",\"host\":\"keybase.io\",\"kid\":\"012094075c68d3c0e3c67ba9309fc43b925aa68586ccf6c278733f85c07f9c62f1c10a\",\"uid\":\"934b8105dc1bd94d8be109b08ef5c119\",\"username\":\"a66714531\"},\"merkle_root\":{\"ctime\":1499358688,\"hash\":\"1dab3ca40bb0eccc8561e20f356bdd4c46cca79e418ccc4280d59ebdac022180d547f8765b1f89fc650c535d65516c96b5e4fc811383b711df550af459dc8deb\",\"hash_meta\":\"96d350830b218ac64ad415c8d94f219a85813d451a6d69fd358badb353ac69b1\",\"seqno\":332460},\"team\":{\"admin\":{\"seq_type\":3,\"seqno\":1,\"team_id\":\"7ebe4dbfe458fde9a5e2ffdc4eb96a24\"},\"id\":\"7ebe4dbfe458fde9a5e2ffdc4eb96a24\",\"invites\":{\"admin\":[{\"id\":\"6acd369ec6f5649c4ef83c8753b3aa27\",\"name\":\"u_8114060fcef4\",\"type\":\"twitter\"}],\"reader\":[{\"id\":\"117b4f1d1048042cb67e204c84d07927\",\"name\":\"u_8114060fcef4\",\"type\":\"reddit\"}],\"writer\":[{\"id\":\"4f66ee0fa60ecb10b9f59ff6b7157527\",\"name\":\"max+8114060fcef4@keyba.se\",\"type\":\"email\"},{\"id\":\"b90e024124ddd80870759bae42143227\",\"name\":\"u_8114060fcef4\",\"type\":\"rooter\"}]}},\"type\":\"team.invite\",\"version\":2},\"ctime\":1499358688,\"expire_in\":157680000,\"prev\":\"eef4dba8c59017876b502eaa5fc013e03f10c9106e79bbacd3b5e2413f6b743f\",\"seq_type\":3,\"seqno\":2,\"tag\":\"signature\"}","version":2,"uid":"934b8105dc1bd94d8be109b08ef5c119"},{"seqno":3,"sig":"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEglAdcaNPA48Z7qTCfxDuSWqaFhsz2wnhzP4XAf5xi8cEKp3BheWxvYWTESJUCA8Qg0sz2c4Djy6V9F8p/maoKpNc4XLhuY+eU585IPs3mOdrEIMS//+b6ohtqz57toWOEF4HKUAHyBQYvGAV9h9NfaszqKKNzaWfEQLf3O7lXJFVK/fetuCPuYqBUeITs7aVBmOVoZm02xRNzNw940jZRAqFZrX7c/6lqqC/ARl+5iLdnbwkErRjcWAmoc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==","payload_json":"{\"body\":{\"key\":{\"eldest_kid\":\"012094075c68d3c0e3c67ba9309fc43b925aa68586ccf6c278733f85c07f9c62f1c10a\",\"host\":\"keybase.io\",\"kid\":\"012094075c68d3c0e3c67ba9309fc43b925aa68586ccf6c278733f85c07f9c62f1c10a\",\"uid\":\"934b8105dc1bd94d8be109b08ef5c119\",\"username\":\"a66714531\"},\"merkle_root\":{\"ctime\":1499358688,\"hash\":\"fdc2a6eb4d685e8b8048946eaf62fe1b5caddb84bcbb1207c1ccf5e65f1d656f5feb4cfc467c121966d6adb128dd8c50b92b64a523f08f8b566b8600df7d9a35\",\"hash_meta\":\"d5818082cee4e8add8081fb605c9cb06cbdc6c4343085d07f9b89e61d2e671fa\",\"seqno\":332461},\"team\":{\"admin\":{\"seq_type\":3,\"seqno\":1,\"team_id\":\"7ebe4dbfe458fde9a5e2ffdc4eb96a24\"},\"id\":\"7ebe4dbfe458fde9a5e2ffdc4eb96a24\",\"invites\":{\"cancel\":[\"117b4f1d1048042cb67e204c84d07927\"]}},\"type\":\"team.invite\",\"version\":2},\"ctime\":1499358689,\"expire_in\":157680000,\"prev\":\"d2ccf67380e3cba57d17ca7f99aa0aa4d7385cb86e63e794e7ce483ecde639da\",\"seq_type\":3,\"seqno\":3,\"tag\":\"signature\"}","version":2,"uid":"934b8105dc1bd94d8be109b08ef5c119"}],"box":{"nonce":"u0oZgbBS5lMOVM9po9vfjwGoxAgAAAAB","sender_kid":"0121d11a0dcb4e0fb545087ef58ff0366ed348f391c9d823ad5ed6616895cc9911030a","generation":1,"ctext":"c9wOION/iDRyTkaN1oTmrSsr/gAgIdULzPqroisEEWU3aCjkGBuu27NqGfXIAe5T","per_user_key_seqno":3},"prevs":{},"reader_key_masks":[{"mask":"2UljdLeivosnFh5Zx0HhPMNYD2n4kz4+cKisRp83q+M=","application":1,"generation":1},{"mask":"gIQztS2j/k26inXIgguOjuk3/AaGfc2thu5pDhtgBvw=","application":2,"generation":1},{"mask":"XGmeo2qgtH62ihZ6hTfmlvGm1PiIkpyyvicUt8VSepk=","application":3,"generation":1}],"id":"7ebe4dbfe458fde9a5e2ffdc4eb96a24","name":{"parts":["t_efc95e26"]},"csrf_token":"lgHZIDkzNGI4MTA1ZGMxYmQ5NGQ4YmUxMDliMDhlZjVjMTE5zlleZeDOAAFRgMDEIGUrg9Ioa5XufDOGCznLyJGlYgcc0d9GzaudNoIqDU8S"}
31`
32
33type DeconstructJig struct {
34	Chain []json.RawMessage `json:"chain"`
35}
36
37func TestTeamSigChainParse(t *testing.T) {
38	tc := SetupTest(t, "test_team_chains", 1)
39	defer tc.Cleanup()
40
41	var jig DeconstructJig
42	err := json.Unmarshal([]byte(teamChain1), &jig)
43	require.NoError(t, err)
44
45	for _, link := range jig.Chain {
46		// t.Logf("link: %v", string(link))
47
48		chainLink, err := ParseTeamChainLink(string(link))
49		require.NoError(t, err)
50
51		t.Logf("chainLink: %v", spew.Sdump(chainLink))
52
53		if len(chainLink.Payload) > 0 {
54			payload, err := chainLink.UnmarshalPayload()
55			require.NoError(t, err)
56			t.Logf("payload: %v", spew.Sdump(payload))
57		} else {
58			t.Logf("payload stubbed")
59		}
60	}
61}
62
63func assertHighSeqForTeam(t *testing.T, tc libkb.TestContext, teamID *keybase1.TeamID, expected int) {
64	team, err := Load(context.TODO(), tc.G, keybase1.LoadTeamArg{
65		ID:          *teamID,
66		ForceRepoll: true,
67	})
68	require.NoError(t, err)
69	actual := int(team.chain().GetLatestHighSeqno())
70	require.Equal(t, expected, actual)
71}
72
73func TestTeamSigChainHighLinks(t *testing.T) {
74	tc := SetupTest(t, "team_sig_chain_high_links", 1)
75	defer tc.Cleanup()
76	ctx := context.TODO()
77
78	// Create some users. The owner is last so that it has the active session.
79	u2, err := kbtest.CreateAndSignupFakeUser("we", tc.G) //admin
80	require.NoError(t, err)
81	u3, err := kbtest.CreateAndSignupFakeUser("ji", tc.G) //non-admin
82	require.NoError(t, err)
83	u4, err := kbtest.CreateAndSignupFakeUser("botua", tc.G) // bot
84	require.NoError(t, err)
85	u5, err := kbtest.CreateAndSignupFakeUser("rua", tc.G) // restricted_bot
86	require.NoError(t, err)
87	u1, err := kbtest.CreateAndSignupFakeUser("je", tc.G) //owner
88	require.NoError(t, err)
89	t.Logf("create the team...")
90	// Create a team. This creates the first high link.
91	teamName := u1.Username + "t"
92	teamNameObj, err := keybase1.TeamNameFromString(teamName)
93	require.NoError(t, err)
94	teamID, err := CreateRootTeam(ctx, tc.G, teamName, keybase1.TeamSettings{})
95	require.NoError(t, err)
96	assertHighSeqForTeam(t, tc, teamID, 1)
97
98	t.Logf("adding new reader...")
99	// Adding a new reader is not a high link, so the lastest high seq won't change.
100	_, err = AddMember(ctx, tc.G, teamName, u3.Username, keybase1.TeamRole_READER, nil)
101	require.NoError(t, err)
102	assertHighSeqForTeam(t, tc, teamID, 1)
103
104	// Adding a new bot is not a high link, so the latest high seq won't
105	// change. Note adding a RESTRICTEDBOT results in two sigs being added, one
106	// for the membership addition and a second for bot_settings.
107	_, err = AddMember(ctx, tc.G, teamName, u4.Username, keybase1.TeamRole_RESTRICTEDBOT, &keybase1.TeamBotSettings{})
108	require.NoError(t, err)
109	assertHighSeqForTeam(t, tc, teamID, 1)
110
111	_, err = AddMember(ctx, tc.G, teamName, u5.Username, keybase1.TeamRole_BOT, nil)
112	require.NoError(t, err)
113	assertHighSeqForTeam(t, tc, teamID, 1)
114
115	t.Logf("adding new admin...")
116	// Adding a new admin IS a high link, so we should jump to 6.
117	_, err = AddMember(ctx, tc.G, teamName, u2.Username, keybase1.TeamRole_ADMIN, nil)
118	require.NoError(t, err)
119	assertHighSeqForTeam(t, tc, teamID, 6)
120
121	t.Logf("promoting from admin to owner...")
122	// Promoting from admin to owner is a high link.
123	err = EditMember(ctx, tc.G, teamName, u2.Username, keybase1.TeamRole_OWNER, nil)
124	require.NoError(t, err)
125	assertHighSeqForTeam(t, tc, teamID, 7)
126
127	t.Logf("demoting from owner to admin...")
128	// Demoting from owner to admin is a high link.
129	err = EditMember(ctx, tc.G, teamName, u2.Username, keybase1.TeamRole_ADMIN, nil)
130	require.NoError(t, err)
131	assertHighSeqForTeam(t, tc, teamID, 8)
132
133	t.Logf("adding new subteam...")
134	// Creating a subteam is not a high link for the parent team
135	// but it is for the subteam. The link types are different
136	// "team.root" and "team.subteam_head" so we should check both teams.
137	sub := "sub"
138	subteamID, err := CreateSubteam(ctx, tc.G, sub, teamNameObj, keybase1.TeamRole_ADMIN)
139	require.NoError(t, err)
140	assertHighSeqForTeam(t, tc, subteamID, 1)
141	assertHighSeqForTeam(t, tc, teamID, 8)
142
143	t.Logf("adding new admin to subteam...")
144	// Adding an admin to the subteam is a high link for the subteam but not
145	// the parent. Primarily, we do this to verify that high links advance
146	// the same way on subteams (since there are places that default to a
147	// value of 1 for sequence number). It's overkill to test any more than
148	// just this on the subteam since it should work the same way.
149	_, err = AddMemberByID(ctx, tc.G, *subteamID, u3.Username, keybase1.TeamRole_ADMIN, nil, nil /* emailInviteMsg */)
150	require.NoError(t, err)
151	assertHighSeqForTeam(t, tc, subteamID, 2)
152	assertHighSeqForTeam(t, tc, teamID, 8)
153
154	t.Logf("demoting admin to writer...")
155	// Back to the root team... downgrading an admin IS a high link for the root team
156	// but it is not one for the subteam, because the subteam needs to care about it's
157	// parent's high links anyway.
158	err = EditMember(ctx, tc.G, teamName, u2.Username, keybase1.TeamRole_WRITER, nil)
159	require.NoError(t, err)
160	assertHighSeqForTeam(t, tc, teamID, 10)
161	assertHighSeqForTeam(t, tc, subteamID, 2)
162
163	t.Logf("demoting admin...")
164	// Back to the root team... downgrading an admin IS a high link for the root team
165	// but it is not one for the subteam, because the subteam needs to care about it's
166	// parent's high links anyway.
167	err = EditMember(ctx, tc.G, teamName, u2.Username, keybase1.TeamRole_WRITER, nil)
168	require.NoError(t, err)
169	assertHighSeqForTeam(t, tc, teamID, 10)
170	assertHighSeqForTeam(t, tc, subteamID, 2)
171
172	t.Logf("rotating keys...")
173	// Rotated keys do not create high links.
174	err = RotateKeyVisible(ctx, tc.G, *teamID)
175	require.NoError(t, err)
176	assertHighSeqForTeam(t, tc, teamID, 10)
177	assertHighSeqForTeam(t, tc, subteamID, 2)
178
179	t.Logf("deleting subteam...")
180	// Deleting a subteam will not create a high link.
181	err = Delete(ctx, tc.G, &teamsUI{}, *subteamID)
182	require.NoError(t, err)
183	assertHighSeqForTeam(t, tc, teamID, 10)
184}
185
186func TestTeamSigChainPlay1(t *testing.T) {
187	tc := SetupTest(t, "test_team_chains", 1)
188	defer tc.Cleanup()
189
190	var jig DeconstructJig
191	err := json.Unmarshal([]byte(teamChain1), &jig)
192	require.NoError(t, err)
193
194	var chainLinks []SCChainLink
195	for i, link := range jig.Chain {
196		// t.Logf("link: %v", string(link))
197
198		chainLink, err := ParseTeamChainLink(string(link))
199		require.NoError(t, err)
200
201		t.Logf("%v chainLink: %v", i, spew.Sdump(chainLink))
202		chainLinks = append(chainLinks, chainLink)
203	}
204
205	consumer := NewUserVersion(keybase1.UID("4bf92804c02fb7d2cd36a6d420d6f619"), 1)
206	var state *TeamSigChainState
207	for _, cLink := range chainLinks {
208		link, err := unpackChainLink(&cLink)
209		require.NoError(t, err)
210		var signer *keybase1.UserVersion
211		if !link.isStubbed() {
212			// Assume the signing user has never reset.
213			signer = &keybase1.UserVersion{
214				Uid:         link.inner.Body.Key.UID,
215				EldestSeqno: keybase1.Seqno(1),
216			}
217		}
218		newState, err := AppendChainLink(context.TODO(), tc.G, consumer, state, link, signerToX(signer))
219		require.NoError(t, err)
220		state = &newState
221	}
222
223	// Check once before and after serializing and deserializing
224	mctx := libkb.NewMetaContextForTest(tc)
225	for i := 0; i < 2; i++ {
226		if i == 0 {
227			t.Logf("testing fresh")
228		} else {
229			t.Logf("testing serde")
230		}
231
232		require.Equal(t, "t_9d6d1e37", string(state.LatestLastNamePart()))
233		require.False(t, state.IsSubteam())
234		ptk, err := state.GetLatestPerTeamKey(mctx)
235		require.NoError(t, err)
236		require.Equal(t, keybase1.PerTeamKeyGeneration(2), ptk.Gen)
237		require.Equal(t, keybase1.Seqno(3), ptk.Seqno)
238		require.Equal(t, "0120802f55ed5e14f61c730871b5e99d637054ff7f5ba0c1b2cc52b154e3baf80a000a", string(ptk.SigKID))
239		require.Equal(t, "0121e2511cbfb0418187a8e19183a1cd92637bc83fe116d1eb8984f52394495b5f120a", string(ptk.EncKID))
240		require.Equal(t, keybase1.Seqno(3), state.GetLatestSeqno())
241		require.Equal(t, state.GetLatestHighSeqno(), keybase1.Seqno(1))
242		require.Equal(t, state.GetLatestHighLinkID(), keybase1.LinkID("62cb761ad346a3d28be1eed107c35548a2dd6fade9219a38591841f62d586cfe"))
243
244		checkRole := func(uid keybase1.UID, role keybase1.TeamRole) {
245			uv := NewUserVersion(uid, 1)
246			r, err := state.GetUserRole(uv)
247			require.NoError(t, err)
248			require.Equal(t, role, r)
249		}
250
251		checkRole("5bf82de4331b50b32cbbcfeadc2f3119", keybase1.TeamRole_OWNER)  // the "doug" user
252		checkRole("53e315afb4b419931b0a6a1eaa09e219", keybase1.TeamRole_ADMIN)  // the "charlie" user
253		checkRole("4bf92804c02fb7d2cd36a6d420d6f619", keybase1.TeamRole_WRITER) // the "bob" user
254		checkRole("13e18aeafa4df6c94bf6af7d7bb98d19", keybase1.TeamRole_READER) // the "alice" user
255		checkRole("popeye", keybase1.TeamRole_NONE)
256
257		linkIDProto := state.GetLatestLinkID()
258		require.Equal(t, "c94234a4855e47d5833a7f43b221ca5e5ccf9970465b77167a793366acf39b16", string(linkIDProto))
259		linkIDLibkb, err := libkb.ImportLinkID(linkIDProto)
260		require.NoError(t, err)
261		require.Equal(t, linkIDProto, linkIDLibkb.Export())
262
263		// Reserialize
264		bs, err := encode(state.inner)
265		require.NoError(t, err, "encode")
266		state = &TeamSigChainState{}
267		err = decode(bs, &state.inner)
268		require.NoError(t, err, "decode")
269	}
270}
271
272func TestTeamSigChainPlay2(t *testing.T) {
273	tc := SetupTest(t, "test_team_chains", 1)
274	defer tc.Cleanup()
275
276	var jig DeconstructJig
277	err := json.Unmarshal([]byte(teamChain2), &jig)
278	require.NoError(t, err)
279
280	var chainLinks []SCChainLink
281	for i, link := range jig.Chain {
282		// t.Logf("link: %v", string(link))
283
284		chainLink, err := ParseTeamChainLink(string(link))
285		require.NoError(t, err)
286
287		t.Logf("%v chainLink: %v", i, spew.Sdump(chainLink))
288		chainLinks = append(chainLinks, chainLink)
289	}
290
291	consumer := NewUserVersion("99759da4f968b16121ece44652f01a19", 1)
292	var state *TeamSigChainState
293	for _, cLink := range chainLinks {
294		link, err := unpackChainLink(&cLink)
295		require.NoError(t, err)
296		var signer *keybase1.UserVersion
297		if !link.isStubbed() {
298			// Assume the signing user has never reset.
299			signer = &keybase1.UserVersion{
300				Uid:         link.inner.Body.Key.UID,
301				EldestSeqno: keybase1.Seqno(1),
302			}
303		}
304		newState, err := AppendChainLink(context.TODO(), tc.G, consumer, state, link, signerToX(signer))
305		require.NoError(t, err)
306		state = &newState
307	}
308	require.NoError(t, err)
309
310	mctx := libkb.NewMetaContextForTest(tc)
311
312	// Check once before and after serializing and deserializing
313	for i := 0; i < 2; i++ {
314		require.Equal(t, "t_bfaadb41", string(state.LatestLastNamePart()))
315		require.False(t, state.IsSubteam())
316		ptk, err := state.GetLatestPerTeamKey(mctx)
317		require.NoError(t, err)
318		require.Equal(t, keybase1.PerTeamKeyGeneration(1), ptk.Gen)
319		require.Equal(t, keybase1.Seqno(1), ptk.Seqno)
320		require.Equal(t, "01208b245208e8951cde7abd3f5ebac5579424e51ed0951fe14ac8a7996c9ccf8ae50a", string(ptk.SigKID))
321		require.Equal(t, "01218ca00b08b4ee5729d957cf14155098b74199588bb5eee778ad1eae58bce26c370a", string(ptk.EncKID))
322		require.Equal(t, keybase1.Seqno(2), state.GetLatestSeqno())
323		require.Equal(t, state.GetLatestHighSeqno(), keybase1.Seqno(1))
324		require.Equal(t, state.GetLatestHighLinkID(), keybase1.LinkID("6828ac32019bd8c0327ea57cd21b6182c3b6e4d4080182bb0119f9a630833f51"))
325
326		checkRole := func(uid keybase1.UID, role keybase1.TeamRole) {
327			uv := NewUserVersion(uid, 1)
328			r, err := state.GetUserRole(uv)
329			require.NoError(t, err)
330			require.Equal(t, role, r)
331		}
332
333		checkRole(keybase1.UID("99759da4f968b16121ece44652f01a19"), keybase1.TeamRole_OWNER)  // the 'doug' user
334		checkRole(keybase1.UID("b720a648e02b99c10d50de0c4f265419"), keybase1.TeamRole_ADMIN)  // the 'charlie' user
335		checkRole(keybase1.UID("921f0e1f2632277cc1fa6600e0906819"), keybase1.TeamRole_WRITER) // the 'bob' user
336		checkRole(keybase1.UID("c8f463c79c83fec675c398b6aa3fa719"), keybase1.TeamRole_WRITER) // changed role for 'alice'; used to be a reader
337
338		xs, err := state.GetUsersWithRole(keybase1.TeamRole_OWNER)
339		require.NoError(t, err)
340		require.Len(t, xs, 1)
341		xs, err = state.GetUsersWithRole(keybase1.TeamRole_WRITER)
342		require.NoError(t, err)
343		require.Len(t, xs, 2)
344		xs, err = state.GetUsersWithRole(keybase1.TeamRole_READER)
345		require.NoError(t, err)
346		require.Len(t, xs, 0)
347		xs, err = state.GetUsersWithRole(keybase1.TeamRole_BOT)
348		require.NoError(t, err)
349		require.Len(t, xs, 0)
350		xs, err = state.GetUsersWithRole(keybase1.TeamRole_RESTRICTEDBOT)
351		require.NoError(t, err)
352		require.Len(t, xs, 0)
353
354		// Reserialize
355		bs, err := encode(state.inner)
356		require.NoError(t, err, "encode")
357		state = &TeamSigChainState{}
358		err = decode(bs, &state.inner)
359		require.NoError(t, err, "decode")
360	}
361}
362
363func encode(input interface{}) ([]byte, error) {
364	mh := codec.MsgpackHandle{WriteExt: true}
365	var data []byte
366	enc := codec.NewEncoderBytes(&data, &mh)
367	if err := enc.Encode(input); err != nil {
368		return nil, err
369	}
370	return data, nil
371}
372
373func decode(data []byte, res interface{}) error {
374	mh := codec.MsgpackHandle{WriteExt: true}
375	dec := codec.NewDecoderBytes(data, &mh)
376	err := dec.Decode(res)
377	return err
378}
379
380func TestTeamSigChainWithInvites(t *testing.T) {
381	tc := SetupTest(t, "test_team_chains", 1)
382	defer tc.Cleanup()
383
384	var jig DeconstructJig
385	err := json.Unmarshal([]byte(teamChainWithInvites), &jig)
386	require.NoError(t, err)
387
388	var chainLinks []SCChainLink
389	for i, link := range jig.Chain {
390
391		chainLink, err := ParseTeamChainLink(string(link))
392		require.NoError(t, err)
393
394		t.Logf("%v chainLink: %v", i, spew.Sdump(chainLink))
395		chainLinks = append(chainLinks, chainLink)
396	}
397
398	consumer := NewUserVersion("99759da4f968b16121ece44652f01a19", 1)
399	var state *TeamSigChainState
400	for _, cLink := range chainLinks {
401		link, err := unpackChainLink(&cLink)
402		require.NoError(t, err)
403		var signer *keybase1.UserVersion
404		if !link.isStubbed() {
405			// Assume the signing user has never reset.
406			signer = &keybase1.UserVersion{
407				Uid:         link.inner.Body.Key.UID,
408				EldestSeqno: keybase1.Seqno(1),
409			}
410		}
411		newState, err := AppendChainLink(context.TODO(), tc.G, consumer, state, link, signerToX(signer))
412		require.NoError(t, err)
413		state = &newState
414	}
415	checkInvite := func(s string, f func(i *keybase1.TeamInvite)) {
416		var i *keybase1.TeamInvite
417		tmpMD, ok := state.FindActiveInviteMDByID(keybase1.TeamInviteID(s))
418		if ok {
419			tmp := tmpMD.Invite
420			i = &tmp
421		}
422		f(i)
423	}
424	checkInvite("117b4f1d1048042cb67e204c84d07927", func(i *keybase1.TeamInvite) {
425		require.Nil(t, i)
426	})
427	checkInvite("b90e024124ddd80870759bae42143227", func(i *keybase1.TeamInvite) {
428		require.NotNil(t, i)
429		require.Equal(t, i.Role, keybase1.TeamRole_WRITER)
430		require.Equal(t, i.Type.Sbs(), keybase1.TeamInviteSocialNetwork("rooter"))
431		require.Equal(t, i.Name, keybase1.TeamInviteName("u_8114060fcef4"))
432	})
433	checkInvite("4f66ee0fa60ecb10b9f59ff6b7157527", func(i *keybase1.TeamInvite) {
434		require.NotNil(t, i)
435		require.Equal(t, i.Role, keybase1.TeamRole_WRITER)
436		typ, err := i.Type.C()
437		require.NoError(t, err)
438		require.Equal(t, typ, keybase1.TeamInviteCategory_EMAIL)
439		require.Equal(t, i.Name, keybase1.TeamInviteName("max+8114060fcef4@keyba.se"))
440	})
441	checkInvite("6acd369ec6f5649c4ef83c8753b3aa27", func(i *keybase1.TeamInvite) {
442		require.NotNil(t, i)
443		require.Equal(t, i.Role, keybase1.TeamRole_ADMIN)
444		require.Equal(t, i.Type.Sbs(), keybase1.TeamInviteSocialNetwork("twitter"))
445		require.Equal(t, i.Name, keybase1.TeamInviteName("u_8114060fcef4"))
446	})
447}
448
449func signerToX(uv *keybase1.UserVersion) *SignerX {
450	if uv == nil {
451		return nil
452	}
453	return &SignerX{signer: *uv}
454}
455
456func TestMemberCtime(t *testing.T) {
457	uv := NewUserVersion("foo", 1)
458	var points []keybase1.UserLogPoint
459	newTeamChainState := func() TeamSigChainState {
460		return TeamSigChainState{
461			inner: keybase1.TeamSigChainState{
462				UserLog: map[keybase1.UserVersion][]keybase1.UserLogPoint{
463					uv: points,
464				},
465			},
466		}
467	}
468
469	// nil points
470	tcs := newTeamChainState()
471	ctime := tcs.MemberCtime(uv)
472	require.Nil(t, ctime)
473
474	// user joined as a writer
475	points = append(points,
476		keybase1.UserLogPoint{
477			Role: keybase1.TeamRole_WRITER,
478			SigMeta: keybase1.SignatureMetadata{
479				Time: 1,
480			},
481		})
482	tcs = newTeamChainState()
483	ctime = tcs.MemberCtime(uv)
484	require.NotNil(t, ctime)
485	require.EqualValues(t, 1, *ctime)
486
487	points = append(points,
488		keybase1.UserLogPoint{
489			Role: keybase1.TeamRole_NONE,
490			SigMeta: keybase1.SignatureMetadata{
491				Time: 2,
492			},
493		},
494		keybase1.UserLogPoint{
495			Role: keybase1.TeamRole_ADMIN,
496			SigMeta: keybase1.SignatureMetadata{
497				Time: 3,
498			},
499		})
500
501	// user left and joined later as an admin
502	tcs = newTeamChainState()
503	ctime = tcs.MemberCtime(uv)
504	require.NotNil(t, ctime)
505	require.EqualValues(t, 3, *ctime)
506
507	// user had a bunch of non-NONE roles, we should return the first join time
508	points = nil
509	for i := 0; i < 5; i++ {
510		points = append(points,
511			keybase1.UserLogPoint{
512				Role: keybase1.TeamRole_WRITER,
513				SigMeta: keybase1.SignatureMetadata{
514					Time: keybase1.Time(i),
515				},
516			})
517		tcs = newTeamChainState()
518		ctime = tcs.MemberCtime(uv)
519		require.NotNil(t, ctime)
520		require.EqualValues(t, 0, *ctime)
521	}
522}
523