10
45
Fork
You've already forked doipjs
28

WIP: LinkedIn Proof #18

Draft
martin.sweeny wants to merge 10 commits from martin.sweeny/doipjs:feature/linkedin-proof into main
pull from: martin.sweeny/doipjs:feature/linkedin-proof
merge into: keyoxide:main
keyoxide:main
keyoxide:dev
keyoxide:fix-irc-fetcher
keyoxide:new-claim-eveonline-143
keyoxide:prepare-new-release
keyoxide:fix-jsdoc-types
keyoxide:yarn-to-npm
keyoxide:support-openpgp-aspe-claims
keyoxide:support-html-alias
keyoxide:improve-activitypub-support
keyoxide:v1-restructure
keyoxide:into-es-module
keyoxide:support-aspe
keyoxide:fix-js-lsp-issues
keyoxide:improve-linting
keyoxide:add-markers
keyoxide:support-opencollective-claim
keyoxide:support-entity-decoding
keyoxide:use-rome-tools
keyoxide:support-cloudflare-buster
keyoxide:support-fediverse-posts
keyoxide:matrix-room-verification
First-time contributor
Copy link

With this PR doip.js should now be able to check for social claims on LinkedIn

Users are expected to write a post with the OpenPGP key comment ( [Verifying my OpenPGP key: openpgp4fpr:$FINGERPRINT]) and are strongly recommended to disable comments on the post to deter fake claims.

The URL is obtained by selected Copy link to post in the post's menu, then the proof notation is simply that url

With this PR doip.js should now be able to check for social claims on LinkedIn Users are expected to write a post with the OpenPGP key comment (` [Verifying my OpenPGP key: openpgp4fpr:$FINGERPRINT]`) and are strongly recommended to disable comments on the post to deter fake claims. The URL is obtained by selected **Copy link to post** in the post's menu, then the proof notation is simply that url
martin.sweeny changed title from (削除) feature/linkedin-proof (削除ここまで) to LinkedIn Proof 2022年02月22日 01:06:15 +01:00
Author
First-time contributor
Copy link

Sorry about the messy commit history. If you're a stickler for clean logs I can squash these into their own branch and open a new PR with a cleaner history

Sorry about the messy commit history. If you're a stickler for clean logs I can squash these into their own branch and open a new PR with a cleaner history
Owner
Copy link

Hey Martin, great work on the PR!

I am not a stickler for clean logs, I have yet to be stung by an unclean one 😄

The docs will indeed need to make it very clear that comments should really really be disabled. We could also look into making our HTML parsing smarter, something we discussed earlier. But I'd rather push for public APIs with JSON formatting.

If you don't mind adding one more commit, that would be great but this one is optional. I have finally added regex to the license check (3dd6b5ae1c), something I should have done a long long time ago. Since this is your contribution, you can now change the license header and add your own name. And change 2021 to 2022 :)

Hey Martin, great work on the PR! I am not a stickler for clean logs, I have yet to be stung by an unclean one 😄 The docs will indeed need to make it very clear that comments should really really be disabled. We could also look into making our HTML parsing smarter, something we discussed earlier. But I'd rather push for public APIs with JSON formatting. If you don't mind adding one more commit, that would be great but this one is optional. I have finally added regex to the license check (3dd6b5ae1c6d4f1fa5c41edf308484cb14021040), something I should have done a long long time ago. Since this is your contribution, you can now change the license header and add your own name. And change 2021 to 2022 :)
chore: update license
All checks were successful
continuous-integration/drone/pr Build is passing
76ef11e6f4
Author
First-time contributor
Copy link

@yarmo Done!

@yarmo Done!
Owner
Copy link

Thx again. Sorry, didn't have time earlier to review. I have one question and two suggestions.

Thx again. Sorry, didn't have time earlier to review. I have [one question and two suggestions](https://codeberg.org/keyoxide/doipjs/pulls/18/files#issuecomment-384166).
First-time contributor
Copy link

Users are expected to write a post with the OpenPGP key comment ( [Verifying my OpenPGP key: openpgp4fpr:$FINGERPRINT]) and are strongly recommended to disable comments on the post to deter fake claims.

What stops me (as a bad actor) from doing the following?

  1. Add the above claim text as a comment to any one of your LinkedIn posts, using my fingerprint (not specifically the same post where you have made your claim and disabled comments)
  2. Add the permalink of that post as a claim notation on my own key
  3. PROFIT!!!

I don't see how the above can be avoided, except by (fragile) HTML parsing to ensure that only the main "post" can contain the key, and not any comments on that post.

> Users are expected to write a post with the OpenPGP key comment ( `[Verifying my OpenPGP key: openpgp4fpr:$FINGERPRINT]`) and are strongly recommended to disable comments on the post to deter fake claims. What stops me (as a bad actor) from doing the following? 1. Add the above claim text as a comment to *any one* of your LinkedIn posts, using my fingerprint (not specifically the same post where you have made your claim and disabled comments) 2. Add the permalink of that post as a claim notation on my own key 3. PROFIT!!! I don't see how the above can be avoided, except by (fragile) HTML parsing to ensure that only the main "post" can contain the key, and not any comments on that post.
Author
First-time contributor
Copy link

@caesar the URL format is different for posts and comments, so a comments' permalink wouldn't work. Or, at least, it shouldn't. I'll add test cases for comments and other permalink formats specifically now that you've mentioned it

@caesar the URL format is different for posts and comments, so a comments' permalink wouldn't work. Or, at least, it shouldn't. I'll add test cases for comments and other permalink formats specifically now that you've mentioned it
Author
First-time contributor
Copy link

Thx again. Sorry, didn't have time earlier to review. I have one question and two suggestions.

I never saw this - I'll take a look later this week.

> Thx again. Sorry, didn't have time earlier to review. I have [one question and two suggestions](https://codeberg.org/keyoxide/doipjs/pulls/18/files#issuecomment-384166). I never saw this - I'll take a look later this week.
First-time contributor
Copy link

the URL format is different for posts and comments, so a comments' permalink wouldn't work

No, I meant add a false claim in a comment on a post, then try to claim using the permalink of the post (not of the comment).

So far as I'm aware comments are displayed on the same page, below the post, so short of parsing the HTML and looking for a certain element id or class (which is fragile), how do you differentiate between the post content and the comment content?

I assume this is why you are saying comments must be turned off on the claim post – but wouldn't the same apply to any other post? Perhaps I've missed something.

> the URL format is different for posts and comments, so a comments' permalink wouldn't work No, I meant add a false claim in a comment on a post, then try to claim using the permalink of the post (not of the comment). So far as I'm aware comments are displayed on the same page, below the post, so short of parsing the HTML and looking for a certain element `id` or `class` (which is fragile), how do you differentiate between the post content and the comment content? I assume this is why you are saying comments must be turned off on the claim post – but wouldn't the same apply to any other post? Perhaps I've missed something.
Author
First-time contributor
Copy link

No, I meant add a false claim in a comment on a post, then try to claim using the permalink of the post (not of the comment).

Oh, right. That is possible, sure. The real account owner would easily see this attempt and could remove it any time, though.

I'd like to see if there's a way we can leverage profile attributes (maybe links?) for proofs instead. 🤔

> No, I meant add a false claim in a comment on a post, then try to claim using the permalink of the post (not of the comment). Oh, right. That _is_ possible, sure. The real account owner would easily see this attempt and could remove it any time, though. I'd like to see if there's a way we can leverage profile attributes (maybe links?) for proofs instead. 🤔
martin.sweeny changed title from (削除) LinkedIn Proof (削除ここまで) to WIP: LinkedIn Proof 2022年03月23日 20:27:16 +01:00
Author
First-time contributor
Copy link

Setting this back to WIP while I improve the test cases

Setting this back to WIP while I improve the test cases
Owner
Copy link

I assume this is why you are saying comments must be turned off on the claim post – but wouldn't the same apply to any other post? Perhaps I've missed something.

Oh shoot. You're absolutely right! Any post could be turned into a "fake proof" by commenting on any post allowing comments.

I had not considered this edge case and is a serious problem.

Sounds like maybe we should go the twitter route and (sadly) go centralized: clients should ask a Keyoxide proxy server to do the verification instead, where the Keyoxide proxy has a Linkedin auth token and access to the official API.

> I assume this is why you are saying comments must be turned off on the claim post – but wouldn't the same apply to any other post? Perhaps I've missed something. Oh shoot. You're absolutely right! Any post could be turned into a "fake proof" by commenting on any post allowing comments. I had not considered this edge case and is a serious problem. Sounds like maybe we should go the twitter route and (sadly) go centralized: clients should ask a Keyoxide proxy server to do the verification instead, where the Keyoxide proxy has a Linkedin auth token and access to the official API.
Author
First-time contributor
Copy link

Actually, upon double-checking, the proof is fine as is.

LinkedIn does not show comments to unauthenticated users!

Check my LinkedIn claim post when logged out (preferably incognito/private window):
https://www.linkedin.com/posts/martinsweeny_verifying-my-openpgp-key-openpgp4fpr08a83f21244c4876d9ec2c6a5f88f7e8f01d4198-activity-6884620821181063168-WsxX

image

Actually, upon double-checking, the proof is fine as is. LinkedIn does not show comments to unauthenticated users! Check my LinkedIn claim post when logged out (preferably incognito/private window): https://www.linkedin.com/posts/martinsweeny_verifying-my-openpgp-key-openpgp4fpr08a83f21244c4876d9ec2c6a5f88f7e8f01d4198-activity-6884620821181063168-WsxX ![image](/attachments/cd0fca1d-e9a9-4ebe-9b14-88c646873581)
martin.sweeny changed title from (削除) WIP: LinkedIn Proof (削除ここまで) to LinkedIn Proof 2022年04月23日 05:46:40 +02:00
Author
First-time contributor
Copy link

Alternatively, there are two other places we can put the proofs:

Contact Info:

image

Featured Items:

image

Semantically, I think this is a better solution

Alternatively, there are two other places we can put the proofs: Contact Info: ![image](/attachments/052e27d5-371a-4691-8072-1d4bc3e631e1) Featured Items: ![image](/attachments/323a05a3-d0a3-4f1e-bd87-c3cfad24dc0d) Semantically, I think this is a better solution
First-time contributor
Copy link

Semantically, I think this is a better solution

I agree. Is there a way for Keyoxide to reliably scrape that data without having an API key?

> Semantically, I think this is a better solution I agree. Is there a way for Keyoxide to reliably scrape that data without having an API key?
Author
First-time contributor
Copy link

Semantically, I think this is a better solution

I agree. Is there a way for Keyoxide to reliably scrape that data without having an API key?

Yup!

image

> > Semantically, I think this is a better solution > > I agree. Is there a way for Keyoxide to reliably scrape that data without having an API key? > > Yup! ![image](/attachments/6afd9e87-ae89-418b-a2dd-31946c01b9d2)
Owner
Copy link

Semantically, I think this is a better solution

I agree. Is there a way for Keyoxide to reliably scrape that data without having an API key?

Yup!

I like the solution, but how exactly is this reliably scrapable? It still requires deep parsing of the HTML, as much of the content is dynamically generated and could thus fool a simple regex query.

> > > Semantically, I think this is a better solution > > > > I agree. Is there a way for Keyoxide to reliably scrape that data without having an API key? > > > > > > Yup! I like the solution, but how exactly is this reliably scrapable? It still requires deep parsing of the HTML, as much of the content is dynamically generated and could thus fool a simple regex query.
First-time contributor
Copy link

This would be a valuable much appreciated addition! Is there anything specific I could help with to get this shipped?

This would be a valuable much appreciated addition! Is there anything specific I could help with to get this shipped?
Owner
Copy link

@sepehr we need a way to reliably get some arbitrary data from a linkedin profile account.

Assume we take the "put the fingerprint in a post" approach. How can we find the fingerprint, given that there is no API?

We could scrape the HTML and look for a fingerprint, sure. But look at the average linkedin post and see how much "extra content" is added, from comments to recommended post. If one of these extra posts also contains a fingerprint, this opens the possibility to impersonation.

Linkedin is not making life easy.

@sepehr we need a way to reliably get some arbitrary data from a linkedin profile account. Assume we take the "put the fingerprint in a post" approach. How can we find the fingerprint, given that there is no API? We could scrape the HTML and look for a fingerprint, sure. But look at the average linkedin post and see how much "extra content" is added, from comments to recommended post. If one of these extra posts also contains a fingerprint, this opens the possibility to impersonation. Linkedin is not making life easy.
Owner
Copy link

Trying something different:

1 - make a post
2 - disable comments (important!)
3 - get the URL from the "embed" option

You get this: https://www.linkedin.com/embed/feed/update/urn:li:share:7034878421184749568

Given that comments have been disabled and this URL shows no junk content, it probably is a safe method. Remains the issue of how to get the username.

We have two solutions:

1 - The user provides the post's normal URL:
https://www.linkedin.com/posts/yarmomackenbach_activity-7034878421637677056-QShF
Using some HTML parsing magic, we can find the urn:li:share:7034878421184749568 snippet and fetch that for the proof data. The URL contains the username. Done.

2 - Alternatively, the user submits directly the "embed URL", the response to which contains the proof data and two URLs to the user (including the username!). So it'll be some parsing hell but at least it's feasible!

Trying something different: 1 - make a post 2 - disable comments (important!) 3 - get the URL from the "embed" option You get this: https://www.linkedin.com/embed/feed/update/urn:li:share:7034878421184749568 Given that comments have been disabled and this URL shows no junk content, it probably is a safe method. Remains the issue of how to get the username. We have two solutions: 1 - The user provides the post's normal URL: https://www.linkedin.com/posts/yarmomackenbach_activity-7034878421637677056-QShF Using some HTML parsing magic, we can find the `urn:li:share:7034878421184749568` snippet and fetch that for the proof data. The URL contains the username. Done. 2 - Alternatively, the user submits directly the "embed URL", the response to which contains the proof data and two URLs to the user (including the username!). So it'll be some parsing hell but at least it's feasible!
First-time contributor
Copy link

Usernames can be changed, no? In the event that they are changed, it will stop working?

Usernames can be changed, no? In the event that they are changed, it will stop working?
Owner
Copy link

Good point! In that case, solution 2 is better, since the username is obtained during the verification process! So the verifier will always get the latest username.

Good point! In that case, solution 2 is better, since the username is obtained during the verification process! So the verifier will always get the latest username.
First-time contributor
Copy link

What's left to do for this?

What's left to do for this?

There is another avenue we could pursue when it comes to getting post content from LinkedIn. LinkedIn posts contain schema.org embedded structured data.

If you take the original @martin.sweeny 's post (https://www.linkedin.com/posts/martinsweeny_verifying-my-openpgp-key-openpgp4fpr08a83f21244c4876d9ec2c6a5f88f7e8f01d4198-activity-6884620821181063168-WsxX) and pass it through schema.org validator you'll see what I'm talking about: https://validator.schema.org/?hl=en-US#url=https://www.linkedin.com/posts/martinsweeny_verifying-my-openpgp-key-openpgp4fpr08a83f21244c4876d9ec2c6a5f88f7e8f01d4198-activity-6884620821181063168-WsxX Expand that SocialMediaPosting item and under $.articleBody you'll find post raw content.
Same applies for later @yarmo 's example (https://www.linkedin.com/posts/yarmomackenbach_activity-7034878421637677056-QShF) as seen here: https://validator.schema.org/?hl=en-US#url=https://www.linkedin.com/posts/yarmomackenbach_activity-7034878421637677056-QShF

What schema.org validator essentially does is:

  1. fetches raw HTML
  2. extracts <script /> element with type attribute set to application/ld+json media type (ld stands for Linked Data)

Here is how you can recreate that flow using curl and pup (and jq):

$ curl -s 'https://www.linkedin.com/posts/martinsweeny_verifying-my-openpgp-key-openpgp4fpr08a83f21244c4876d9ec2c6a5f88f7e8f01d4198-activity-6884620821181063168-WsxX' \
 | pup 'script[type=application/ld+json] text{}' \
 | jq

which produces the following output:

{
 "@context": "http://schema.org",
 "@type": "SocialMediaPosting",
 "@id": "https://www.linkedin.com/posts/martinsweeny_verifying-my-openpgp-key-openpgp4fpr08a83f21244c4876d9ec2c6a5f88f7e8f01d4198-activity-6884620821181063168-WsxX",
 "author": {
 "name": "Martin â–1⁄2 Sweeny",
 "image": {
 "url": "https://media.licdn.com/dms/image/D4E03AQEPi12R3WJiLg/profile-displayphoto-shrink_200_200/0/1693155735016?e=2147483647&v=beta&t=F9Zmr1SdpAZJ-tvqRRCzIk7Gnugofrj48Y6c-NUGw2w",
 "@type": "ImageObject"
 },
 "url": "https://ca.linkedin.com/in/martinsweeny",
 "@type": "Person"
 },
 "datePublished": "2022年01月05日T22:25:13.826Z",
 "articleBody": "[Verifying my OpenPGP key: openpgp4fpr:08a83f21244c4876d9ec2c6a5f88f7e8f01d4198]",
 "hasPart": {
 "@type": "WebPageElement",
 "isAccessibleForFree": false,
 "cssSelector": ".details"
 }
}

Ignore mangled $.author.name, that's just pup's broken utf8 handling 🤷

Now let me take a step back and address the issue of proof URL format. First of all every post on LinkedIn is uniquely represented by URN of the following form urn:li:activity:{activity_id}. If you know that URN you can easily construct:

  1. direct post URL: https://www.linkedin.com/feed/update/urn:li:activity:{activity_id}
  2. embed post URL: https://www.linkedin.com/embed/feed/update/urn:li:activity:{activity_id}

If you look at returned HTML more closely you'll notice <meta /> element whose property attribute is set to lnkd:url and content holds a direct post URL in the earlier described form. Let's extract that out from @martin.sweeny 's post:

$ curl -s 'https://www.linkedin.com/posts/martinsweeny_verifying-my-openpgp-key-openpgp4fpr08a83f21244c4876d9ec2c6a5f88f7e8f01d4198-activity-6884620821181063168-WsxX' \
 | pup '[property=lnkd:url] attr{content}'
#==> https://www.linkedin.com/feed/update/urn:li:activity:6884620821181063168

and with a little bit of sed magic we can extract raw URN too:

$ curl -s 'https://www.linkedin.com/posts/martinsweeny_verifying-my-openpgp-key-openpgp4fpr08a83f21244c4876d9ec2c6a5f88f7e8f01d4198-activity-6884620821181063168-WsxX' \
 | pup '[property=lnkd:url] attr{content}' \
 | sed 's|.*/||'
#==> urn:li:activity:6884620821181063168

If you now compare the original post URL with the extracted URN you'll notice that activity_id is actually part of the original URL: https://www.linkedin.com/posts/martinsweeny_{slug}-activity-{activity_id}-WsxX so we don't need that network roundtrip at all:

$ echo 'https://www.linkedin.com/posts/martinsweeny_verifying-my-openpgp-key-openpgp4fpr08a83f21244c4876d9ec2c6a5f88f7e8f01d4198-activity-6884620821181063168-Wsx' \
 | sed 's|.*activity-|urn:li:activity:|; s|-.*$||'
#==> urn:li:activity:6884620821181063168

So to summarize all this, LinkedIn accounts can indeed be verified through posts on the platform since post pages contain embedded structured data (that belongs to schema.org vocabulary) and potential proof URL formats are:

  1. post URL with user-friendly slug: https://www.linkedin.com/posts/{username}_{slug}-activity-{activity_id}-...
  2. resolved activity (post) URN: https://www.linkedin.com/feed/update/urn:li:activity:{activity_id}
  3. activity (post) URN: urn:li:activity:{activity_id}

However, in order to any of this to happen new schema.org fetcher needs to be implemented both by doip-js client library and the Keyoxide proxy server.

PS To prevent accidental merges I'm moving this PR back to the draft phase.

There is another avenue we could pursue when it comes to getting post content from LinkedIn. LinkedIn posts contain [schema.org](https://schema.org) embedded structured data. If you take the original @martin.sweeny 's post (https://www.linkedin.com/posts/martinsweeny_verifying-my-openpgp-key-openpgp4fpr08a83f21244c4876d9ec2c6a5f88f7e8f01d4198-activity-6884620821181063168-WsxX) and pass it through schema.org validator you'll see what I'm talking about: <https://validator.schema.org/?hl=en-US#url=https://www.linkedin.com/posts/martinsweeny_verifying-my-openpgp-key-openpgp4fpr08a83f21244c4876d9ec2c6a5f88f7e8f01d4198-activity-6884620821181063168-WsxX> Expand that `SocialMediaPosting` item and under `$.articleBody` you'll find post raw content. Same applies for later @yarmo 's example (https://www.linkedin.com/posts/yarmomackenbach_activity-7034878421637677056-QShF) as seen here: <https://validator.schema.org/?hl=en-US#url=https://www.linkedin.com/posts/yarmomackenbach_activity-7034878421637677056-QShF> What schema.org validator essentially does is: 1. fetches raw HTML 2. extracts `<script />` element with `type` attribute set to `application/ld+json` media type (ld stands for Linked Data) Here is how you can recreate that flow using `curl` and [`pup`](https://github.com/ericchiang/pup) (and `jq`): ```sh $ curl -s 'https://www.linkedin.com/posts/martinsweeny_verifying-my-openpgp-key-openpgp4fpr08a83f21244c4876d9ec2c6a5f88f7e8f01d4198-activity-6884620821181063168-WsxX' \ | pup 'script[type=application/ld+json] text{}' \ | jq ``` which produces the following output: ```json { "@context": "http://schema.org", "@type": "SocialMediaPosting", "@id": "https://www.linkedin.com/posts/martinsweeny_verifying-my-openpgp-key-openpgp4fpr08a83f21244c4876d9ec2c6a5f88f7e8f01d4198-activity-6884620821181063168-WsxX", "author": { "name": "Martin â–1⁄2 Sweeny", "image": { "url": "https://media.licdn.com/dms/image/D4E03AQEPi12R3WJiLg/profile-displayphoto-shrink_200_200/0/1693155735016?e=2147483647&v=beta&t=F9Zmr1SdpAZJ-tvqRRCzIk7Gnugofrj48Y6c-NUGw2w", "@type": "ImageObject" }, "url": "https://ca.linkedin.com/in/martinsweeny", "@type": "Person" }, "datePublished": "2022年01月05日T22:25:13.826Z", "articleBody": "[Verifying my OpenPGP key: openpgp4fpr:08a83f21244c4876d9ec2c6a5f88f7e8f01d4198]", "hasPart": { "@type": "WebPageElement", "isAccessibleForFree": false, "cssSelector": ".details" } } ``` _Ignore mangled `$.author.name`, that's just `pup`'s broken utf8 handling 🤷_ Now let me take a step back and address the issue of proof URL format. First of all every post on LinkedIn is uniquely represented by URN of the following form `urn:li:activity:{activity_id}`. If you know that URN you can easily construct: 1. direct post URL: `https://www.linkedin.com/feed/update/urn:li:activity:{activity_id}` 2. embed post URL: `https://www.linkedin.com/embed/feed/update/urn:li:activity:{activity_id}` If you look at returned HTML more closely you'll notice `<meta />` element whose `property` attribute is set to `lnkd:url` and `content` holds a direct post URL in the earlier described form. Let's extract that out from @martin.sweeny 's post: ```sh $ curl -s 'https://www.linkedin.com/posts/martinsweeny_verifying-my-openpgp-key-openpgp4fpr08a83f21244c4876d9ec2c6a5f88f7e8f01d4198-activity-6884620821181063168-WsxX' \ | pup '[property=lnkd:url] attr{content}' #==> https://www.linkedin.com/feed/update/urn:li:activity:6884620821181063168 ``` and with a little bit of `sed` magic we can extract raw URN too: ```sh $ curl -s 'https://www.linkedin.com/posts/martinsweeny_verifying-my-openpgp-key-openpgp4fpr08a83f21244c4876d9ec2c6a5f88f7e8f01d4198-activity-6884620821181063168-WsxX' \ | pup '[property=lnkd:url] attr{content}' \ | sed 's|.*/||' #==> urn:li:activity:6884620821181063168 ``` If you now compare the original post URL with the extracted URN you'll notice that `activity_id` is actually part of the original URL: `https://www.linkedin.com/posts/martinsweeny_{slug}-activity-{activity_id}-WsxX` so we don't need that network roundtrip at all: ```sh $ echo 'https://www.linkedin.com/posts/martinsweeny_verifying-my-openpgp-key-openpgp4fpr08a83f21244c4876d9ec2c6a5f88f7e8f01d4198-activity-6884620821181063168-Wsx' \ | sed 's|.*activity-|urn:li:activity:|; s|-.*$||' #==> urn:li:activity:6884620821181063168 ``` So to summarize all this, LinkedIn accounts can indeed be verified through posts on the platform since post pages contain embedded structured data (that belongs to schema.org vocabulary) and potential proof URL formats are: 1. post URL with user-friendly slug: `https://www.linkedin.com/posts/{username}_{slug}-activity-{activity_id}-...` 2. resolved activity (post) URN: `https://www.linkedin.com/feed/update/urn:li:activity:{activity_id}` 3. activity (post) URN: `urn:li:activity:{activity_id}` However, in order to any of this to happen new schema.org fetcher needs to be implemented both by doip-js client library and the Keyoxide proxy server. PS To prevent accidental merges I'm moving this PR back to the draft phase.
vladimyr changed title from (削除) LinkedIn Proof (削除ここまで) to WIP: LinkedIn Proof 2024年03月04日 08:35:05 +01:00

@yarmo wrote:

1 - The user provides the post's normal URL:
https://www.linkedin.com/posts/yarmomackenbach_activity-7034878421637677056-QShF
Using some HTML parsing magic, we can find the urn:li:share:7034878421184749568 snippet and fetch that for the proof data. The URL contains the username. Done.

BTW share URNs are totally different beasts, and you can extract one from a post page HTML page using:

$ curl -s 'https://www.linkedin.com/posts/yarmomackenbach_activity-7034878421637677056-QShF' \
 | pup '[data-attributed-urn^=urn:li:share] attr{data-attributed-urn}' \
 | head -1
#==> urn:li:share:7034878421184749568

They partially match activity URNs:

  1. share URN: urn:li:share:7034878421_184749568
  2. activity URN: urn:li:activity:7034878421_637677056

⚠️ underscore has been added just for the sake of easier comparison

@yarmo wrote: > 1 - The user provides the post's normal URL: > https://www.linkedin.com/posts/yarmomackenbach_activity-7034878421637677056-QShF > Using some HTML parsing magic, we can find the `urn:li:share:7034878421184749568` snippet and fetch that for the proof data. The URL contains the username. Done. BTW share URNs are totally different beasts, and you can extract one from a post page HTML page using: ```sh $ curl -s 'https://www.linkedin.com/posts/yarmomackenbach_activity-7034878421637677056-QShF' \ | pup '[data-attributed-urn^=urn:li:share] attr{data-attributed-urn}' \ | head -1 #==> urn:li:share:7034878421184749568 ``` They partially match activity URNs: 1. share URN: `urn:li:share:7034878421_184749568` 2. activity URN: `urn:li:activity:7034878421_637677056` _:warning: underscore has been added just for the sake of easier comparison_
Owner
Copy link

This is pretty neat! It's unfortunate we can't get the JSON directly from an endpoint, but interesting that it's in a script[type=application/ld+json] element. This looks like a viable method.

This is pretty neat! It's unfortunate we can't get the JSON directly from an endpoint, but interesting that it's in a `script[type=application/ld+json]` element. This looks like a viable method.

Indeed, btw that's how Google rich results extract data from result pages.

Indeed, btw that's how Google rich results extract data from result pages.
First-time contributor
Copy link

@martin.sweeny Any update on this?

@martin.sweeny Any update on this?
All checks were successful
continuous-integration/drone/pr Build is passing
This pull request has changes conflicting with the target branch.
  • src/claimDefinitions/index.js
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u feature/linkedin-proof:martin.sweeny-feature/linkedin-proof
git switch martin.sweeny-feature/linkedin-proof
Sign in to join this conversation.
No reviewers
Labels
Clear labels
bug
Something is not working

Archived

enhancement
New feature

Archived

Contribution welcome
Get started contributing here
Good first issue
Good if you are new to the project or to open source contributions
Impact
External
Affects the people using the project
Impact
Internal
Affects on the people working on the project
Priority
Critical
Work on right now
Priority
High
Work on at earliest convenience
Priority
Low
Work on in spare time
Priority
Medium
Work on regularly
Review
Duplicate
Already exists
Review
Off Topic
Does not fall within the scope of the repo/project
Status
Backlog
Is not being worked on yet
Status
Blocked
Is waiting on something or someone
Status
Completed
Is done
Status
In Progress
Is being worked on
Status
Investigating
Is waiting on research or questions
Status
Needs Decision
Is waiting on a decision by the devs/contributors
Status
Needs Info
Is waiting on additional information before it can be solved
Status
Needs Triage
Is new issue that needs reviewing
Status
Testing
Is being checked and verified
Status
Waiting For Review
Is waiting on reviewers to approve
Status
Won't Fix
Won't be fixed
Type
Bug
Related to something not working as intended
Type
CI
Related to continuous integration
Type
Documentation
Related to documentation
Type
Enhancement
Related to a new feature or an improved one
Type
New Claim
Related to a new identity claim/proof
Type
Security
Related to security
Type
Tests
Related to code tests
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
8 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
keyoxide/doipjs!18
Reference in a new issue
keyoxide/doipjs
No description provided.
Delete branch "martin.sweeny/doipjs:feature/linkedin-proof"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?